Adamw has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/122220

Change subject: WIP Standard libraries; integration tests
......................................................................

WIP Standard libraries; integration tests

Change-Id: I98114dbdeeac5a12a50ef6252120d12fa2ee34bd
---
A includes/library/Alarm.php
A includes/library/MachineHierarchy.php
A includes/library/SelfStimulating.php
A includes/library/TaggedPage.php
A includes/library/WikiPages.php
A tests/BaseWorkflowTestCase.php
A tests/DoorLibrary.php
A tests/TestStateMachine.php
A tests/TestStateMachineDescription.php
A tests/ennui_machine.yaml
10 files changed, 474 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Workflow 
refs/changes/20/122220/1

diff --git a/includes/library/Alarm.php b/includes/library/Alarm.php
new file mode 100644
index 0000000..f4e5177
--- /dev/null
+++ b/includes/library/Alarm.php
@@ -0,0 +1,86 @@
+<?php namespace Workflow\Library;
+
+use \Job;
+
+/**
+ * Simple deferred self-signaling
+ */
+class Alarm
+       implements IMachineLibrary
+{
+       protected $actions;
+
+       function __construct() {
+               $this->actions = array(
+                       'alarm' => new AlarmAction(),
+               );
+       }
+
+       function getActions() {
+               return $this->actions;
+       }
+}
+
+class AlarmAction
+       implements ITransactionalAction
+{
+       protected $job;
+
+       function begin() {}
+
+       /**
+        * @param array $params (
+        *     'signal' => string signal name
+        *     'interval' => string relative time interval
+        * )
+        */
+       function run( IStateMachine $machine, $params ) {
+               global $wgJobClasses;
+
+               list ( $signal, $interval ) = $params;
+
+               $scheduledTime = strtotime( $interval );
+               // TODO: cannot schedule job time yet.  See 
https://bugzilla.wikimedia.org/show_bug.cgi?id=60217
+
+               $this->job = new AlarmJob( null, array(
+                       // FIXME: I suspect there are some limitations on 
$params
+                       // serializability; we might need to pass job_id here 
rather than
+                       // $machine.
+                       'machine' => $machine,
+                       'signal' => $signal,
+               ) );
+
+               $wgJobClasses[$this->job->getCommand()] = get_class( $this->job 
);
+               JobQueueGroup::singleton()->push( $this->job );
+       }
+
+       function commit() {}
+
+       function rollback() {
+               // TODO: detect when we are too late to prevent the job, and... 
do something.
+               JobQueueGroup::singleton()->ack( $this->job );
+       }
+}
+
+class AlarmJob
+       extends Job
+{
+       static protected $command = 'workflowAlarmJob';
+
+       protected $machine;
+       protected $signal;
+
+       function getCommand() {
+               return static::$command;
+       }
+
+       function __construct( $title, $params ) {
+               parent::__construct( static::$command, $title, $params );
+               $this->machine = $params['machine'];
+               $this->signal = $params['signal'];
+       }
+
+       function run() {
+               $this->machine->signal( $this->signal );
+       }
+}
diff --git a/includes/library/MachineHierarchy.php 
b/includes/library/MachineHierarchy.php
new file mode 100644
index 0000000..acac4bc
--- /dev/null
+++ b/includes/library/MachineHierarchy.php
@@ -0,0 +1,16 @@
+<?php namespace Workflow\Library;
+
+/**
+ * Supports subtasks which are themselves a state machine
+ */
+class MachineHierarchy
+       implements IMachineLibrary
+{
+       protected $actions = array(
+               'submachine',
+       );
+
+       function handle_submachine( IStateMachine $machine, $params ) {
+               // TODO design. message passing.
+       }
+}
diff --git a/includes/library/SelfStimulating.php 
b/includes/library/SelfStimulating.php
new file mode 100644
index 0000000..e5f0359
--- /dev/null
+++ b/includes/library/SelfStimulating.php
@@ -0,0 +1,50 @@
+<?php namespace Workflow\Library;
+
+use Workflow\IStateMachine;
+
+/**
+ * Use this mixin to implement a machine which can send itself signals.  
Without
+ * this, control flow is always relinquished after entering a state.
+ */
+class SelfStimulating
+       implements IMachineLibrary
+{
+       protected $actions;
+
+       function __construct() {
+               $this->actions = array(
+                       'signal' => new SignalAction(),
+               );
+       }
+
+       function getActions() {
+               return $this->actions;
+       }
+}
+
+class SignalAction
+       implements ITransactionalAction
+{
+       protected $machine;
+       protected $signal;
+
+       function beginTransaction() {}
+
+       /**
+        * @param array $params (
+        *     string $signal - name of the signal to send
+        * );
+        */
+       function run( IStateMachine $machine, $params ) {
+               $this->machine = $machine;
+
+               // parse params
+               list( $this->signal ) = $params;
+       }
+
+       function commitTransaction() {
+               $this->machine->signal( $this->signal );
+       }
+
+       function rollbackTransaction() {}
+}
diff --git a/includes/library/TaggedPage.php b/includes/library/TaggedPage.php
new file mode 100644
index 0000000..db8817f
--- /dev/null
+++ b/includes/library/TaggedPage.php
@@ -0,0 +1,52 @@
+<?php namespace Workflow\Library;
+
+use Workflow\IStateMachine;
+
+/**
+ * Utilities to tag pages with a category, or assert that a page is or is not 
already tagged with a category
+ */
+class TaggedPage
+       implements IMachineLibrary
+{
+       protected $actions;
+
+       function __construct() {
+               $this->actions = array(
+                       'tag' => new TagAction(),
+                       'untag' => new UntagAction(),
+                       'assert_tagged' => new AssertTaggedAction(),
+                       'assert_untagged' => new AssertUntaggedAction(),
+               );
+       }
+
+       function getActions() {
+               return $this->actions;
+       }
+}
+
+class TagAction
+       implements ITransactionalAction
+{
+       protected $machine;
+       protected $signal;
+
+       function begin() {}
+
+       /**
+        * @param array $params (
+        *     string $signal - name of the signal to send
+        * );
+        */
+       function run( IStateMachine $machine, $params ) {
+               $this->machine = $machine;
+
+               // parse params
+               list( $this->signal ) = $params;
+       }
+
+       function commit() {
+               $this->machine->signal( $this->signal );
+       }
+
+       function rollback() {}
+}
diff --git a/includes/library/WikiPages.php b/includes/library/WikiPages.php
new file mode 100644
index 0000000..0ef220e
--- /dev/null
+++ b/includes/library/WikiPages.php
@@ -0,0 +1,42 @@
+<?php namespace Workflow\Library;
+
+use Workflow\Exception\InvalidActionException;
+
+/**
+ * Support UI flow elements
+ */
+class WikiPages
+       implements IMachineLibrary
+{
+       function getActions() {
+               return array(
+                       'redirect',
+               );
+       }
+
+       function handleAction( IStateMachine $machine, $action, $params ) {
+               switch ( $action ) {
+               case 'redirect':
+                       $this->handle_redirect( $machine, $params );
+                       break;
+               default:
+                       throw new InvalidActionException( $action );
+               }
+       }
+
+       /**
+        * @param array $params (
+        *     string $titleVar - name of the configuration variable holding 
the target page title
+        * );
+        *
+        * TODO: whitelist page review status
+        */
+       function handle_redirect( $machine, $params ) {
+               list( $titleVar ) = $params;
+
+               $output = RequestContext::getMain()->getOutput();
+               $key = $machine->getValue( $titleVar );
+               $url = Title::newFromText( $key )->getFullURL();
+               $output->redirect( $url );
+       }
+}
diff --git a/tests/BaseWorkflowTestCase.php b/tests/BaseWorkflowTestCase.php
new file mode 100644
index 0000000..07eb9ef
--- /dev/null
+++ b/tests/BaseWorkflowTestCase.php
@@ -0,0 +1,20 @@
+<?php namespace Workflow\Tests;
+
+use \MediaWikiTestCase;
+
+use Workflow\Description\DescriptionLoader;
+use Workflow\StateMachine;
+
+class BaseWorkflowTestCase extends MediaWikiTestCase {
+       protected $description;
+       protected $machine;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $descriptionPath = __DIR__ . '/ennui_machine.yaml';
+
+               $this->description = DescriptionLoader::loadFromFile( 
$descriptionPath );
+               $this->machine = new StateMachine( $this->description );
+       }
+}
diff --git a/tests/DoorLibrary.php b/tests/DoorLibrary.php
new file mode 100644
index 0000000..3d21d77
--- /dev/null
+++ b/tests/DoorLibrary.php
@@ -0,0 +1,51 @@
+<?php namespace Workflow\Tests;
+
+use Workflow\IStateMachine;
+use Workflow\Library\IAction;
+use Workflow\Library\IMachineLibrary;
+use Workflow\Exception\TransitionAbortedException;
+
+class DoorLibrary
+       implements IMachineLibrary
+{
+       const DOOR_VAR = 'door_locked';
+
+       function getActions() {
+               return array(
+                       'lock' => new LockAction(),
+                       'unlock' => new UnlockAction(),
+                       'assert_unlocked' => new AssertUnlockedAction(),
+               );
+       }
+}
+
+class LockAction
+       implements IAction
+{
+       function run( IStateMachine $machine, $params ) {
+               $machine->setValue( DoorLibrary::DOOR_VAR, true );
+       }
+}
+
+class UnlockAction
+       implements IAction
+{
+       function run( IStateMachine $machine, $params ) {
+               $machine->setValue( DoorLibrary::DOOR_VAR, false );
+       }
+}
+
+class AssertUnlockedAction
+       implements IAction
+{
+       function run( IStateMachine $machine, $params ) {
+               if ( $machine->getValue( DoorLibrary::DOOR_VAR ) ) {
+                       throw new LockedException();
+               }
+       }
+}
+
+class LockedException
+       extends TransitionAbortedException
+{
+}
diff --git a/tests/TestStateMachine.php b/tests/TestStateMachine.php
new file mode 100644
index 0000000..d247091
--- /dev/null
+++ b/tests/TestStateMachine.php
@@ -0,0 +1,73 @@
+<?php namespace Workflow\Tests;
+
+/**
+ * @group Workflow
+ */
+class TestStateMachine extends BaseWorkflowTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->machine->start();
+       }
+
+       function testInitialState() {
+               $this->assertSame( 'inside', $this->machine->getState() );
+       }
+
+       function testTransition() {
+               $this->machine->signal( 'walk' );
+
+               $this->assertSame( 'at_threshold', $this->machine->getState() );
+       }
+
+       /**
+        * @expectedException Workflow\Exception\InvalidTransitionException
+        */
+       function testInvalidTransition() {
+               $this->machine->signal( 'bogon' );
+       }
+
+       function testExceptionSignal() {
+               $this->machine->signal( 'give_up' );
+
+               $this->assertSame( 'trapped', $this->machine->getState() );
+       }
+
+       function testConfiguration() {
+               $this->assertSame( false, $this->machine->getValue( 
'door_locked' ) );
+       }
+
+       function testTransitionAction() {
+               $this->machine->signal( 'walk' );
+               $this->machine->signal( 'use_key' );
+
+               $this->assertSame( true, $this->machine->getValue( 
'door_locked' ) );
+       }
+
+       function testStateAction() {
+               $this->machine->signal( 'walk' );
+               $this->machine->signal( 'open_door' );
+               $this->machine->signal( 'exit' );
+
+               $this->assertSame( 'gone', $this->machine->getState() );
+       }
+
+       function testCompletion() {
+               $this->machine->signal( 'walk' );
+               $this->machine->signal( 'open_door' );
+               $this->machine->signal( 'exit' );
+
+               $this->assertTrue( $this->machine->isFinished() );
+       }
+
+       function testRollback() {
+               $this->machine->signal( 'walk' );
+               $this->machine->signal( 'use_key' );
+
+               // Locked door will prevent the transition
+               $this->machine->signal( 'open_door' );
+
+               $this->assertSame( 'at_threshold', $this->machine->getState() );
+       }
+}
diff --git a/tests/TestStateMachineDescription.php 
b/tests/TestStateMachineDescription.php
new file mode 100644
index 0000000..3609cdb
--- /dev/null
+++ b/tests/TestStateMachineDescription.php
@@ -0,0 +1,47 @@
+<?php namespace Workflow\Tests;
+
+/**
+ * @group Workflow
+ */
+class TestStateMachineDescription extends BaseWorkflowTestCase {
+
+       function testLoadYamlDescription() {
+               $this->assertNotNull( $this->description );
+       }
+
+       function testInitialState() {
+               $this->assertEquals( 'inside', 
$this->description->getInitialState() );
+       }
+
+       function testGetTransitionsForState() {
+               $transitions = $this->description->getTransitionsForState( 
'inside' );
+               sort( $transitions );
+               $this->assertSame( array( 'fidget', 'walk' ), $transitions );
+       }
+
+       function testGetDestinationState() {
+               $this->assertSame( 'at_threshold', 
$this->description->getTransition( 'inside', 'walk' )->getDestinationName() );
+       }
+
+       function testGetExceptions() {
+               $this->assertSame( array( 'give_up' ), 
$this->description->getExceptions() );
+       }
+
+       function testGetActionsForTransition() {
+               $actionInvocations = $this->description->getTransition( 
'at_threshold', 'use_key' )->getActionInvocations();
+               $this->assertSame( array( 'lock' ), array_keys( 
$actionInvocations ) );
+               $this->assertSame( null, 
$actionInvocations['lock']->getParams() );
+       }
+
+       function testGetActionsForState() {
+               $this->assertSame( array(), 
$this->description->getActionsForState( 'at_threshold' ) );
+
+               $actionInvocations = $this->description->getActionsForState( 
'at_liberty' );
+               $this->assertSame( array( 'signal' ), array_keys( 
$actionInvocations ) );
+               $this->assertSame( 'exit', 
$actionInvocations['signal']->getParams() );
+       }
+
+       function testGetConfigurationValue() {
+               $this->assertSame( false, 
$this->description->getConfigurationValue( 'door_locked' ) );
+       }
+}
diff --git a/tests/ennui_machine.yaml b/tests/ennui_machine.yaml
new file mode 100644
index 0000000..b8c774c
--- /dev/null
+++ b/tests/ennui_machine.yaml
@@ -0,0 +1,37 @@
+# Common-sensical demonstration FSM
+title: Test Machine
+
+# FIXME: these PHP namespaces jump out like a second thumb, oh well.
+libraries:
+    - Workflow\Library\SelfStimulating
+    - Workflow\Tests\DoorLibrary
+
+states:
+    - inside:
+        transitions:
+            walk: at_threshold
+            fidget: inside
+    - at_threshold:
+        transitions:
+            open_door:
+                actions:
+                    - assert_unlocked:
+                destination: at_liberty
+            return: inside
+            use_key:
+                actions:
+                    - lock:
+                destination: at_threshold
+    - at_liberty:
+        actions:
+            - signal: exit
+        transitions:
+            exit: gone
+    - gone:
+    - trapped:
+
+exceptions:
+    give_up: trapped
+
+configuration:
+    door_locked: false

-- 
To view, visit https://gerrit.wikimedia.org/r/122220
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I98114dbdeeac5a12a50ef6252120d12fa2ee34bd
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Workflow
Gerrit-Branch: master
Gerrit-Owner: Adamw <awi...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to