cuke4php 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ ?>