cuke4php 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ <?php
2
+ /**
3
+ * @package Cuke4Php
4
+ */
5
+
6
+ /**
7
+ * Base class that all cucumber steps should derive from
8
+ *
9
+ * Each step gets a reference to the global variables array provided by the scenario.
10
+ * This allows steps to save and maintain state over the course of a scenario run.
11
+ *
12
+ * This class also inherits PHPUnit assertions, so those can all be used within the context of a
13
+ * step. Data providers probably don't work, and really should not be used in any case.
14
+ * @package Cuke4Php
15
+ */
16
+ class CucumberSteps extends PHPUnit_Framework_Assert {
17
+ static private $aMocks = array();
18
+ protected $aGlobals;
19
+
20
+ public function __construct(&$_aGlobals) {
21
+ $this->aGlobals =& $_aGlobals;
22
+ }
23
+
24
+ public static function markPending($sMessage = "Not Implemented") {
25
+ self::markTestIncomplete($sMessage);
26
+ }
27
+
28
+ public static function getSubclasses() {
29
+ $aClasses = array();
30
+ foreach (get_declared_classes() as $sClassName) {
31
+ if (is_subclass_of($sClassName, 'CucumberSteps') && (stripos($sClassName,"Mock") === false))
32
+ $aClasses[] = $sClassName;
33
+ }
34
+ return $aClasses;
35
+ }
36
+
37
+ /**
38
+ * @param $sMethod
39
+ * @return mixed
40
+ */
41
+ function invoke($sMethod) {
42
+ return $this->$sMethod();
43
+ }
44
+
45
+ /**
46
+ * @static
47
+ * @param $sClass
48
+ * @param $oMock
49
+ * @return void
50
+ *
51
+ * Allows tests to set mock hooks to be used
52
+ */
53
+ static function setMock($sClass, $oMock) {
54
+ self::$aMocks[$sClass] = $oMock;
55
+ }
56
+
57
+ static function clearMocks() {
58
+ self::$aMocks = array();
59
+ }
60
+
61
+ /**
62
+ * @static
63
+ * @param $sClass
64
+ * @param $aGlobals
65
+ * @return
66
+ *
67
+ * Get an instance of a hook which is either a pre-set mock,
68
+ * or an instance of the appropriate step class with the globals initialized
69
+ */
70
+ static function getInstance($sClass, $aGlobals) {
71
+ if (array_key_exists($sClass, self::$aMocks) && self::$aMocks[$sClass]) {
72
+ return self::$aMocks[$sClass];
73
+ } else {
74
+ return new $sClass($aGlobals);
75
+ }
76
+ }
77
+ }
78
+
79
+ ?>
data/lib/Cuke4Php.php ADDED
@@ -0,0 +1,242 @@
1
+ <?php
2
+ /**
3
+ * @package Cuke4Php
4
+ */
5
+
6
+ set_time_limit(0);
7
+
8
+
9
+ /**
10
+ * Cuke4Php implements the Cucumber wire protocol for PHP
11
+ *
12
+ * http://wiki.github.com/aslakhellesoy/cucumber/wire-protocol
13
+ * @package Cuke4Php
14
+ */
15
+ class Cuke4Php {
16
+ public $iPort;
17
+ private $bRun;
18
+ private $oSocket;
19
+ private $oScenario;
20
+ private $aStepClasses;
21
+ public $aWorld = array(
22
+ 'steps' => array(),
23
+ 'before' => array(),
24
+ 'after' => array()
25
+ );
26
+
27
+ function __construct($_sFeaturePath, $_iPort = 16816) {
28
+ if ($_iPort > 0) {
29
+ $this->iPort = $_iPort;
30
+ } else {
31
+ $this->iPort = 16816;
32
+ }
33
+
34
+ foreach (self::rglob("*.php", 0, $_sFeaturePath . "/support") as $sFilename) {
35
+ require_once $sFilename;
36
+ }
37
+ require_once "Cucumber.php";
38
+ foreach (self::rglob("*.php", 0, $_sFeaturePath . "/step_definitions") as $sFilename) {
39
+ require_once $sFilename;
40
+ }
41
+ $this->aStepClasses = CucumberSteps::getSubclasses();
42
+ foreach ($this->aStepClasses as $sClass) {
43
+ $oReflection = new ReflectionClass($sClass);
44
+ $aMethods = $oReflection->getMethods();
45
+ foreach ($aMethods as $oMethod) {
46
+ $sComment = $oMethod->getDocComment();
47
+ $aMatches = array();
48
+ $aMethod = array();
49
+ $aMethod['method'] = $oMethod->name;
50
+ $aMethod['class'] = $oMethod->class;
51
+ $aMethod['filename'] = $oMethod->getFileName();
52
+ $aMethod['startline'] = $oMethod->getStartLine();
53
+ if (substr($oMethod->name, 0, 4) === "step") {
54
+ preg_match("/(?:Given|When|Then) (.+)$/im", $sComment, $aMatches);
55
+ $aMethod['regexp'] = $aMatches[1];
56
+ $this->aWorld['steps'][] = $aMethod;
57
+ continue;
58
+ }
59
+ preg_match("/(@.+)/im", $sComment, $aMatches);
60
+ if (array_key_exists(1, $aMatches)) {
61
+ $aMethod['tags'] = explode(" ", str_replace("@", "", $aMatches[1]));
62
+ }
63
+ if (substr($oMethod->name, 0, 6) === "before") {
64
+ $this->aWorld['before'][] = $aMethod;
65
+ continue;
66
+ }
67
+ if (substr($oMethod->name, 0, 5) === "after") {
68
+ $this->aWorld['after'][] = $aMethod;
69
+ continue;
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param string $pattern
77
+ * @param int $flags
78
+ * @param string $path
79
+ * @return array
80
+ * recursive glob utility function
81
+ */
82
+ static function rglob($sPattern='*', $iFlags = 0, $sPath='') {
83
+ $aPaths=glob($sPath.'*', GLOB_MARK|GLOB_ONLYDIR|GLOB_NOSORT);
84
+ $aFiles=glob($sPath.$sPattern, $iFlags);
85
+ foreach ($aPaths as $sPath) { $aFiles=array_merge($aFiles,self::rglob($sPattern, $iFlags, $sPath)); }
86
+ return $aFiles;
87
+ }
88
+
89
+
90
+ /**
91
+ * @param $oScenario
92
+ * @return void
93
+ */
94
+ function setScenario($oScenario) {
95
+ $this->oScenario = $oScenario;
96
+ }
97
+
98
+ function __destruct() {
99
+ if (isset($this->oSocket) && $this->oSocket) {
100
+ socket_close($this->oSocket);
101
+ }
102
+ }
103
+
104
+ function run() {
105
+ print "Cuke4Php listening on port $this->iPort\n";
106
+ $this->oSocket = socket_create_listen($this->iPort);
107
+ $this->bRun = true;
108
+ while ($this->bRun && ($connection = socket_accept($this->oSocket))) {
109
+ socket_getpeername($connection, $raddr, $rport);
110
+ while ($this->bRun && ($input = socket_read($connection, 1024 * 4))) {
111
+ $data = trim($input);
112
+ if ($data !== "") {
113
+ $output = json_encode($this->process($data)) . "\n";
114
+ if ($this->bRun) {
115
+ socket_write($connection, $output);
116
+ }
117
+ }
118
+ }
119
+ socket_close($connection);
120
+ sleep(1);
121
+ }
122
+ }
123
+
124
+ function process($sInput) {
125
+ switch ($sInput) {
126
+ case "quit":
127
+ case "bye":
128
+ $this->bRun = false;
129
+ return "Complete";
130
+ break;
131
+ default:
132
+ $aCommand = json_decode($sInput);
133
+ $sAction = $aCommand[0];
134
+ $sData = NULL;
135
+ if (array_key_exists(1, $aCommand)) {
136
+ $sData = $aCommand[1];
137
+ }
138
+ switch ($sAction) {
139
+ case 'begin_scenario':
140
+ return $this->beginScenario($sData->tags);
141
+ break;
142
+ case 'step_matches':
143
+ return $this->stepMatches($sData->name_to_match);
144
+ break;
145
+ case 'invoke':
146
+ return $this->oScenario->invoke($sData->id, $sData->args);
147
+ break;
148
+ case 'end_scenario':
149
+ return $this->endScenario($sData->tags);
150
+ break;
151
+ case 'snippet_text':
152
+ return $this->snippetText($sData);
153
+ default:
154
+ print "Unknown Command: $sInput\n";
155
+ break;
156
+ }
157
+ return array('success');
158
+ break;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * @param $aTags
164
+ * @return array
165
+ * run any before hooks for a scenario
166
+ */
167
+ function beginScenario($aTags) {
168
+ $this->setScenario(CucumberScenario::getInstance($this->aWorld));
169
+ return $this->oScenario->invokeBeforeHooks($aTags);
170
+ }
171
+
172
+ /**
173
+ * @param $sStep
174
+ * @return array
175
+ * when given a string, this method will return information about any step that matches it
176
+ */
177
+ function stepMatches($sStep) {
178
+ $aSteps = array();
179
+ for ($i = 0; $i < count($this->aWorld['steps']); $i++) {
180
+ $aMatches = array();
181
+ $aStep = $this->aWorld['steps'][$i];
182
+ if (preg_match_all($aStep['regexp'], $sStep, $aMatches, PREG_OFFSET_CAPTURE)) {
183
+ $aArgs = array();
184
+ array_shift($aMatches);
185
+ foreach ($aMatches as $aMatch) {
186
+ $aArgs[] = array('val' => $aMatch[0][0], 'pos' => $aMatch[0][1]);
187
+ }
188
+ $aSteps[] = array('id' => $i, 'args' => $aArgs, 'source' => $aStep['filename'] . ":" . $aStep['startline']);
189
+ };
190
+ }
191
+ return array('success', $aSteps);
192
+ }
193
+
194
+ /**
195
+ * @param $aTags
196
+ * @return array
197
+ * run any after hooks for a scenario
198
+ */
199
+ function endScenario($aTags) {
200
+ $oResult = $this->oScenario->invokeAfterHooks($aTags);
201
+ $this->oScenario = null;
202
+ return $oResult;
203
+ }
204
+
205
+ /**
206
+ * @param $aSnippet
207
+ * @return array
208
+ * return a template for an undefined step
209
+ */
210
+ function snippetText($aSnippet) {
211
+ $sMethodName = "step" . str_replace(" ", "", ucwords(preg_replace("/\W+/", " ", preg_replace("/\"[^\"]*\"/", "Parameter", $aSnippet->step_name))));
212
+ $count = 0;
213
+ $aParams = array();
214
+ $sStepName = preg_replace("/\"[^\"]*\"/", "\"([^\"]*)\"", preg_quote($aSnippet->step_name), -1, &$count);
215
+ for ($param = 1; $param <= $count; $param++) {
216
+ $aParams[] = "\$arg$param";
217
+ }
218
+ switch ($aSnippet->multiline_arg_class) {
219
+ case "Cucumber::Ast::Table":
220
+ $aParams[] = "\$aTable";
221
+ break;
222
+ case "Cucumber::Ast::PyString":
223
+ $aParams[] = "\$sString";
224
+ break;
225
+ default:
226
+ }
227
+
228
+ $sParams = implode(",", $aParams);
229
+ $sMethodBody = <<<EOT
230
+
231
+ /**
232
+ * {$aSnippet->step_keyword} /^$sStepName$/
233
+ **/
234
+ public function $sMethodName($sParams) {
235
+ self::markPending();
236
+ }
237
+ EOT;
238
+ return array('success', $sMethodBody);
239
+ }
240
+ }
241
+
242
+ ?>
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env php
2
+ <?php
3
+ /**
4
+ * main entry point for starting a Cuke4Php wire server
5
+ * @package Cuke4Php
6
+ */
7
+
8
+ /**
9
+ * load the Cuke4Php server
10
+ */
11
+ require_once dirname(__FILE__) . "/../lib/Cuke4Php.php";
12
+ $aOptions = getopt("p:");
13
+ if (array_key_exists('p',$aOptions)) {
14
+ $iPort = $aOptions['p'];
15
+ } else {
16
+ $iPort = null;
17
+ }
18
+ $oServer = new Cuke4Php(realpath($argv[$argc-1]), $iPort);
19
+ $oServer->run();
20
+ ?>
@@ -0,0 +1,161 @@
1
+ <?php
2
+ /**
3
+ * @package Cuke4Php
4
+ */
5
+
6
+ /**
7
+ * load Cucumber php framework
8
+ */
9
+ require_once dirname(__FILE__) . "/../../lib/Cucumber.php";
10
+
11
+ /**
12
+ * class TestException
13
+ * this class is used during the tests to mock exceptions.
14
+ * @package Cuke4Php
15
+ */
16
+ class TestException extends Exception {
17
+ public function __toString() {
18
+ return "TestException";
19
+ }
20
+ }
21
+
22
+ require_once(dirname(__FILE__) . "/../../features/step_definitions/TestSteps.php");
23
+
24
+ /**
25
+ * class CucumberScenarioTest
26
+ * @package Cuke4Php
27
+ */
28
+ class CucumberScenarioTest extends PHPUnit_Framework_TestCase {
29
+
30
+ public $oScenario;
31
+ public $aWorld;
32
+ public $aTags;
33
+ public $oMockHook;
34
+
35
+ public function setup() {
36
+ CucumberSteps::clearMocks();
37
+ $this->aWorld = array(
38
+ 'before' => array(
39
+ array(
40
+ 'tags' => array('one'),
41
+ 'class' => 'TestSteps',
42
+ 'method' => 'beforeWithOneTag'
43
+ ),
44
+ array(
45
+ 'tags' => array(),
46
+ 'class' => 'TestSteps',
47
+ 'method' => 'beforeWithNoTags'
48
+ )
49
+ ),
50
+ 'after' => array(
51
+ array(
52
+ 'tags' => array('one'),
53
+ 'class' => 'TestSteps',
54
+ 'method' => 'afterWithOneTag'
55
+ ),
56
+ array(
57
+ 'tags' => array(),
58
+ 'class' => 'TestSteps',
59
+ 'method' => 'afterWithNoTags'
60
+ )
61
+ ),
62
+ 'steps' => array(
63
+ 0 => array(
64
+ 'class' => 'TestSteps',
65
+ 'method' => 'stepSuccessful'
66
+ ),
67
+ 1 => array(
68
+ 'class' => 'TestSteps',
69
+ 'method' => 'stepIncomplete'
70
+ ),
71
+ 2 => array(
72
+ 'class' => 'TestSteps',
73
+ 'method' => 'stepSkipped'
74
+ ),
75
+ 3 => array(
76
+ 'class' => 'TestSteps',
77
+ 'method' => 'stepPending'
78
+ ),
79
+ 4 => array(
80
+ 'class' => 'TestSteps',
81
+ 'method' => 'stepFailed'
82
+ ),
83
+ 5 => array(
84
+ 'class' => 'TestSteps',
85
+ 'method' => 'stepException'
86
+ ),
87
+ 6 => array(
88
+ 'class' => 'TestSteps',
89
+ 'method' => 'stepNotEqual'
90
+ )
91
+
92
+ )
93
+
94
+ );
95
+ $this->aTags = array('one','two');
96
+ $this->oScenario = new CucumberScenario($this->aWorld);
97
+ $this->oMockHook = $this->getMock('TestSteps', array(
98
+ 'beforeWithOneTag',
99
+ 'beforeWithNoTags',
100
+ 'afterWithOneTag',
101
+ 'afterWithNoTags'), array(array()));
102
+ }
103
+
104
+ public function testShouldRunBeforeHooksWithAMatchingTag() {
105
+ $this->oMockHook->expects(self::once())->method('beforeWithOneTag');
106
+ $this->oMockHook->expects(self::once())->method('beforeWithNoTags');
107
+ CucumberSteps::setMock('TestSteps', $this->oMockHook);
108
+ self::assertEquals(array('success'),$this->oScenario->invokeBeforeHooks($this->aTags));
109
+ }
110
+
111
+ public function testShouldRunBeforeHooksWithNoTags() {
112
+ $this->oMockHook->expects(self::once())->method('beforeWithNoTags');
113
+ CucumberSteps::setMock('TestSteps', $this->oMockHook);
114
+ self::assertEquals(array('success'),$this->oScenario->invokeBeforeHooks(array()));
115
+ }
116
+
117
+
118
+ public function testShouldRunAfterHooksWithAMatchingTag() {
119
+ $this->oMockHook->expects(self::once())->method('afterWithOneTag');
120
+ $this->oMockHook->expects(self::once())->method('afterWithNoTags');
121
+ CucumberSteps::setMock('TestSteps', $this->oMockHook);
122
+ self::assertEquals(array('success'),$this->oScenario->invokeAfterHooks($this->aTags));
123
+ }
124
+
125
+ public function testShouldRunAfterHooksWithNoTags() {
126
+ $this->oMockHook->expects(self::once())->method('afterWithNoTags');
127
+ CucumberSteps::setMock('TestSteps', $this->oMockHook);
128
+ self::assertEquals(array('success'),$this->oScenario->invokeAfterHooks(array()));
129
+ }
130
+
131
+ public function testInvokeShouldReturnSuccess() {
132
+ self::assertEquals(array('success'),$this->oScenario->invoke(0,array()));
133
+ }
134
+
135
+ public function testInvokeShouldReturnPendingWhenIncomplete() {
136
+ self::assertEquals(array('pending','incomplete'),$this->oScenario->invoke(1,array()));
137
+ }
138
+
139
+ public function testInvokeShouldReturnPendingWhenSkipped() {
140
+ self::assertEquals(array('pending','skipped'),$this->oScenario->invoke(2,array()));
141
+ }
142
+
143
+ public function testInvokeShouldReturnPendingWhenPending() {
144
+ self::assertEquals(array('pending','pending'),$this->oScenario->invoke(3,array()));
145
+ }
146
+
147
+ public function testInvokeShouldFailWhenAssertionNotMet() {
148
+ self::assertEquals(array('fail',array('message' => 'Failed asserting that <boolean:false> is equal to <boolean:true>.')), $this->oScenario->invoke(4,array()));
149
+ }
150
+
151
+ public function testInvokeShouldFailWhenExceptionThrown() {
152
+ self::assertEquals(array('fail',array('exception' => 'TestException')), $this->oScenario->invoke(5,array()));
153
+ }
154
+
155
+ public function testInvokeShouldSucceedWithParameters() {
156
+ self::assertEquals(array('success'), $this->oScenario->invoke(6,array('one','two')));
157
+ }
158
+
159
+ }
160
+
161
+ ?>
@@ -0,0 +1,117 @@
1
+ <?php
2
+ /**
3
+ * @package Cuke4Php
4
+ */
5
+
6
+ /**
7
+ * load Cucumber framework
8
+ */
9
+ require_once dirname(__FILE__) . "/../../lib/Cucumber.php";
10
+
11
+ /**
12
+ * @package Cuke4Php
13
+ */
14
+ class Cuke4PhpTest extends PHPUnit_Framework_TestCase {
15
+
16
+ public $oCuke4Php;
17
+
18
+ function setup() {
19
+ $this->oCuke4Php = new Cuke4Php(dirname(__FILE__) . "/../../features");
20
+ }
21
+
22
+ function testSnippetTextReturnsSnippet() {
23
+ $oSnippet = new StdClass;
24
+ $oSnippet->step_name = "this is a step name";
25
+ $oSnippet->multiline_arg_class = "";
26
+ $oSnippet->step_keyword = "Given";
27
+ $sActual = $this->oCuke4Php->snippetText($oSnippet);
28
+ $sExpected = array('success',"
29
+ /**
30
+ * Given /^this is a step name$/
31
+ **/
32
+ public function stepThisIsAStepName() {
33
+ self::markPending();
34
+ }");
35
+ self::assertEquals($sExpected, $sActual);
36
+ }
37
+
38
+ function testSnippetTextReturnsSnippetWithParameters() {
39
+ $oSnippet = new StdClass;
40
+ $oSnippet->step_name = 'this is a step with parameter "param1"';
41
+ $oSnippet->multiline_arg_class = "";
42
+ $oSnippet->step_keyword = "Given";
43
+ $sActual = $this->oCuke4Php->snippetText($oSnippet);
44
+ $sExpected = array('success','
45
+ /**
46
+ * Given /^this is a step with parameter "([^"]*)"$/
47
+ **/
48
+ public function stepThisIsAStepWithParameterParameter($arg1) {
49
+ self::markPending();
50
+ }');
51
+ self::assertEquals($sExpected, $sActual);
52
+ }
53
+
54
+ function testSnippetTextReturnsSnippetWithATable() {
55
+ $oSnippet = new StdClass;
56
+ $oSnippet->step_name = 'this is a step with a table:';
57
+ $oSnippet->multiline_arg_class = "Cucumber::Ast::Table";
58
+ $oSnippet->step_keyword = "Given";
59
+ $sActual = $this->oCuke4Php->snippetText($oSnippet);
60
+ $sExpected = array('success','
61
+ /**
62
+ * Given /^this is a step with a table\:$/
63
+ **/
64
+ public function stepThisIsAStepWithATable($aTable) {
65
+ self::markPending();
66
+ }');
67
+ self::assertEquals($sExpected, $sActual);
68
+ }
69
+
70
+ function testSnippetTextReturnsSnippetWithAMultilineString() {
71
+ $oSnippet = new StdClass;
72
+ $oSnippet->step_name = 'this is a step with a pystring:';
73
+ $oSnippet->multiline_arg_class = "Cucumber::Ast::PyString";
74
+ $oSnippet->step_keyword = "Given";
75
+ $sActual = $this->oCuke4Php->snippetText($oSnippet);
76
+ $sExpected = array('success','
77
+ /**
78
+ * Given /^this is a step with a pystring\:$/
79
+ **/
80
+ public function stepThisIsAStepWithAPystring($sString) {
81
+ self::markPending();
82
+ }');
83
+ self::assertEquals($sExpected, $sActual);
84
+ }
85
+
86
+ function testBeginScenarioShouldInvokeBeforeHooks() {
87
+ $oMockScenario = $this->getMock('CucumberScenario', array('invokeBeforeHooks'));
88
+ $oMockScenario->expects(self::once())->method('invokeBeforeHooks');
89
+ CucumberScenario::setInstance($oMockScenario);
90
+ $this->oCuke4Php->beginScenario(array());
91
+ }
92
+
93
+ function testEndScenarioShouldInvokeAfterHooks() {
94
+ $oMockScenario = $this->getMock('CucumberScenario', array('invokeAfterHooks'));
95
+ $oMockScenario->expects(self::once())->method('invokeAfterHooks');
96
+ $this->oCuke4Php->setScenario($oMockScenario);
97
+ $this->oCuke4Php->endScenario(array());
98
+ }
99
+
100
+ function testStepMatchesShouldReturnEmptySetWhenNoMatches() {
101
+ self::assertEquals(array('success',array()), $this->oCuke4Php->stepMatches("random step"));
102
+ }
103
+
104
+ function testStepMatchesShouldReturnMatches() {
105
+ self::assertEquals(array('success',array(
106
+ array('id' => 0, 'args' => array(), 'source' => realpath(dirname(__FILE__) . '/../../features/step_definitions/TestSteps.php') . ":14")
107
+ )), $this->oCuke4Php->stepMatches("successful"));
108
+ }
109
+
110
+ function testStepMatchesShouldReturnMatchesWithParameters() {
111
+ self::assertEquals(array('success',array(
112
+ array('id' => 6, 'args' => array(), 'source' => realpath(dirname(__FILE__) . '/../../features/step_definitions/TestSteps.php') . ":54")
113
+ )), $this->oCuke4Php->stepMatches('"arg1" not equal to "arg2"'));
114
+ }
115
+
116
+ }
117
+ ?>