Skip to content

Commit 2f4b626

Browse files
committed
Merge branch 'feature/refactor-tests' into develop
2 parents c27df7d + b3cc2a0 commit 2f4b626

File tree

8 files changed

+487
-29
lines changed

8 files changed

+487
-29
lines changed

PHPCtags.class.php

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ class PHPCtags
77

88
private $mParser;
99

10-
private $mOptions;
11-
12-
public function __construct($file, $options=array())
10+
public function __construct()
1311
{
14-
//@todo Check for existence
15-
$this->mFile = $file;
1612
$this->mKinds= array(
1713
'c' => 'class',
1814
'm' => 'method',
@@ -23,7 +19,6 @@ public function __construct($file, $options=array())
2319
'i' => 'interface',
2420
);
2521
$this->mParser = new PHPParser_Parser(new PHPParser_Lexer);
26-
$this->mOptions = $options;
2722
}
2823

2924
private function getNodeAccess($node)
@@ -143,7 +138,7 @@ private function struct($node, $parent=array())
143138
return $structs;
144139
}
145140

146-
private function render($structs)
141+
private function render($structs, $options)
147142
{
148143
$str = '';
149144
$lines = file($this->mFile);
@@ -159,37 +154,37 @@ private function render($structs)
159154

160155
$str .= "\t" . $this->mFile;
161156

162-
if ($this->mOptions['excmd'] == 'number') {
157+
if ($options['excmd'] == 'number') {
163158
$str .= "\t" . $struct['line'];
164159
} else { //excmd == 'mixed' or 'pattern', default behavior
165160
$str .= "\t" . "/^" . rtrim($lines[$struct['line'] - 1], "\n") . "$/";
166161
}
167162

168-
if ($this->mOptions['format'] == 1) {
163+
if ($options['format'] == 1) {
169164
$str .= "\n";
170165
continue;
171166
}
172167

173168
$str .= ";\"";
174169

175170
#field=k, kind of tag as single letter
176-
if (in_array('k', $this->mOptions['fields'])) {
177-
in_array('z', $this->mOptions['fields']) && $str .= "kind:";
171+
if (in_array('k', $options['fields'])) {
172+
in_array('z', $options['fields']) && $str .= "kind:";
178173
$str .= "\t" . $struct['kind'];
179174
} else
180175
#field=K, kind of tag as fullname
181-
if (in_array('K', $this->mOptions['fields'])) {
182-
in_array('z', $this->mOptions['fields']) && $str .= "kind:";
176+
if (in_array('K', $options['fields'])) {
177+
in_array('z', $options['fields']) && $str .= "kind:";
183178
$str .= "\t" . $this->mKinds[$struct['kind']];
184179
}
185180

186181
#field=n
187-
if (in_array('n', $this->mOptions['fields'])) {
182+
if (in_array('n', $options['fields'])) {
188183
$str .= "\t" . "line:" . $struct['line'];
189184
}
190185

191186
#field=s
192-
if (in_array('s', $this->mOptions['fields']) && !empty($struct['scope'])) {
187+
if (in_array('s', $options['fields']) && !empty($struct['scope'])) {
193188
$scope = array_pop($struct['scope']);
194189
list($type,$name) = each($scope);
195190
switch ($type) {
@@ -206,7 +201,7 @@ private function render($structs)
206201
}
207202

208203
#field=a
209-
if (in_array('a', $this->mOptions['fields']) && !empty($struct['access'])) {
204+
if (in_array('a', $options['fields']) && !empty($struct['access'])) {
210205
$str .= "\t" . "access:" . $struct['access'];
211206
}
212207

@@ -215,9 +210,11 @@ private function render($structs)
215210
return $str;
216211
}
217212

218-
public function export()
213+
public function export($file, $options)
219214
{
215+
//@todo Check for existence
216+
$this->mFile = $file;
220217
$structs = $this->struct($this->mParser->parse(file_get_contents($this->mFile)));
221-
echo $this->render($structs);
218+
echo $this->render($structs,$options);
222219
}
223220
}

phpctags

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ if(!isset($options['fields'])) {
4444
$options['fields'] = str_split($options['fields']);
4545
}
4646

47-
$ctags = new PHPCtags($file, $options);
48-
$ctags->export();
47+
$ctags = new PHPCtags();
48+
$ctags->export($file, $options);

tests/PHPCtagsTest.php

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<?php
2+
require_once __DIR__ . '/PHPCtagsTestCase.php';
3+
24
/**
35
* Generated by PHPUnit_SkeletonGenerator on 2012-07-10 at 02:00:19.
46
*/
@@ -15,13 +17,7 @@ class PHPCtagsTest extends PHPUnit_Framework_TestCase
1517
*/
1618
protected function setUp()
1719
{
18-
$options = array(
19-
'excmd' => 'pattern',
20-
'fields' => array('n','k','s','S','a'),
21-
'format' => 2,
22-
);
23-
24-
$this->object = new PHPCtags(__DIR__ . '/PHPCtagsTest.example.php', $options);
20+
$this->object = new PHPCtags();
2521
}
2622

2723
/**
@@ -32,12 +28,59 @@ protected function tearDown()
3228
{
3329
}
3430

31+
/**
32+
* Data provider
33+
**/
34+
public function provider()
35+
{
36+
$testcases_files = array();
37+
$testcases = scandir(__DIR__ . '/testcases');
38+
foreach($testcases as $testcase)
39+
{
40+
if($testcase === '.' || $testcase === '..') {
41+
continue;
42+
}
43+
$testcases_files[] = array($testcase);
44+
}
45+
return $testcases_files;
46+
}
47+
3548
/**
3649
* @covers PHPCtags::export
50+
* @dataProvider provider
3751
*/
38-
public function testExport()
52+
public function testExport($testcase)
3953
{
40-
$this->object->export();
54+
require_once __DIR__ . '/testcases/' . $testcase;
55+
$testcase_id = strstr($testcase, '.', true);
56+
$testcase_class = 't_' . $testcase_id;
57+
$testcase_object = new $testcase_class;
58+
59+
$testcase_expect = $testcase_object->getExpectResult();
60+
61+
ob_start();
62+
$testcase_example = $testcase_object->getExample();
63+
$testcase_options = $testcase_object->getOptions();
64+
$this->object->export($testcase_example, $testcase_options);
65+
$testcase_result = ob_get_contents();
66+
ob_end_clean();
67+
68+
$expected_result = __DIR__ . '/' . $testcase_id . '.testcase.expect';
69+
$acctural_result = __DIR__ . '/' . $testcase_id . '.testcase.result';
70+
71+
$msg = <<<EOF
72+
Test case '$testcase' has failed.
73+
Expected result has been dumped to '$expected_result'
74+
Acctural result has been dumped to '$acctural_result'
75+
EOF;
76+
77+
try {
78+
$this->assertEquals(md5($testcase_expect), md5($testcase_result), $msg);
79+
} catch (PHPUnit_Framework_ExpectationFailedException $e) {
80+
file_put_contents($expected_result, $testcase_expect);
81+
file_put_contents($acctural_result, $testcase_result);
82+
throw $e;
83+
}
4184
}
4285

4386
}

tests/PHPCtagsTestCase.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
abstract class PHPCtagsTestCase {
4+
5+
protected $mFormat;
6+
7+
protected $mOptions;
8+
9+
protected $mExample;
10+
11+
public function __construct()
12+
{
13+
$this->mFormat = "<name>\t<file>\t/^<line content>$/;\"\t<kind>\tline:<line number>\t<scope>\t<access>";
14+
$this->mOptions = array(
15+
'excmd' => 'pattern',
16+
'fields' => array('n','k','s','a'),
17+
'format' => 2,
18+
);
19+
}
20+
21+
public function getOptions()
22+
{
23+
return $this->mOptions;
24+
}
25+
26+
public function getExample()
27+
{
28+
return realpath(__DIR__ . '/examples/' . $this->mExample . '.example.php');
29+
}
30+
31+
public function getExampleDefine()
32+
{
33+
require_once __DIR__ . '/examples/' . $this->mExample . '.example.define.php';
34+
if (function_exists('e_' . $this->mExample . '_define')) {
35+
$define = call_user_func('e_' . $this->mExample . '_define');
36+
} else {
37+
die('example definition not exist');
38+
}
39+
return $define;
40+
}
41+
42+
public function getExampleContent()
43+
{
44+
return file($this->getExample());
45+
}
46+
47+
public function getExpectResult()
48+
{
49+
$testcase_expect = '';
50+
$testcase_example_define = $this->getExampleDefine();
51+
$testcase_example_content = $this->getExampleContent();
52+
foreach ($testcase_example_define as $define) {
53+
$line = $this->mFormat;
54+
55+
$line = preg_replace('/<name>/', $define['name'], $line);
56+
$line = preg_replace('/<file>/', $this->getExample(), $line);
57+
$line = preg_replace('/<line content>/', rtrim($testcase_example_content[$define['line'] - 1], "\n"), $line);
58+
$line = preg_replace('/<kind>/', $define['kind'], $line);
59+
$line = preg_replace('/<line number>/', $define['line'], $line);
60+
if(!empty($define['scope'])) {
61+
$line = preg_replace('/<scope>/', $define['scope'], $line);
62+
} else {
63+
$line = preg_replace('/<scope>/', '', $line);
64+
}
65+
if(!empty($define['access'])) {
66+
$line = preg_replace('/<access>/', 'access:' . $define['access'], $line);
67+
} else {
68+
$line = preg_replace('/<access>/', '', $line);
69+
}
70+
$line = rtrim($line, "\t");
71+
$line .= "\n";
72+
73+
$testcase_expect .= $line;
74+
}
75+
76+
return $testcase_expect;
77+
78+
}
79+
}

tests/README.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
How to write a test case
2+
========================
3+
4+
We are using [PHPUnit][] for testing.
5+
6+
[PHPUnit]:https://github.com/sebastianbergmann/phpunit/
7+
8+
This directory contains test cases for phpctags. The structure is shown as
9+
below.
10+
11+
```
12+
tests/
13+
|--examples/
14+
| |-- <example_identifier>.example.php
15+
| |-- <example_identifier>.example.define.php
16+
| |-- ...
17+
|--testcases/
18+
| |-- <testcase_identifier>.testcase.php
19+
| |-- ...
20+
|--bootstrap.php
21+
|--PHPCtagsTest.php
22+
|--PHPCtagsTestCase.php
23+
````
24+
25+
`examples/` directory holds the example file for testing named as
26+
`<example_identifier>.example.php` and its token definition file named
27+
as `<example_identifier>.example.define.php`. The `<example_identifier>`
28+
should be unique and only contians characters in the range of `[0-9a-zA-Z_]`,
29+
especially should not contain a dot.
30+
31+
File `<example_identifiler>.example.define.php` contains only one function
32+
named as `e_<example_identifier>_define` which returns an array contains
33+
information about tokens in the corresponding example file. This array consists
34+
of a set of arrays ordered by the occurrence order of the tokens in the example
35+
file. Each of these arrays consits of the following keys.
36+
37+
* `name`
38+
39+
Required, name of the token
40+
41+
* `line`
42+
43+
Required, line number of the token
44+
45+
* `kind`
46+
47+
Required, kind descriptor of the token in one character.
48+
For a full definition kind descriptor, please see `PHPCtags::$mKinds`.
49+
50+
* `scope`
51+
52+
Optional, scope definition of the token
53+
54+
* `access`
55+
56+
Optional, access restriction for the token
57+
58+
For example, assuming we have the following code in an example file named as
59+
`foo.example.php`.
60+
61+
```
62+
<?php
63+
class Foo {
64+
public function bar() {
65+
}
66+
}
67+
```
68+
69+
Then, our `foo.example.define.php` should contain the following code which
70+
reflect the token information about the example code above and will be used
71+
to generate the expected result.
72+
73+
```
74+
<?php
75+
function e_foo_define()
76+
{
77+
return array(
78+
array(
79+
'name'=>'Foo',
80+
'line'=>'2',
81+
'kind'=>'c',
82+
),
83+
array(
84+
'name'=>'bar',
85+
'line'=>'3',
86+
'kind'=>'m',
87+
'scope'=>'class:Foo',
88+
'access'=>'public',
89+
),
90+
);
91+
}
92+
```
93+
94+
After finishing adding the example files, it is the time to add a test case for
95+
this example. All test cases are located at `testcases/` directory and named
96+
as `<testcase_identifier>.<testcase_description>.testcase.php`. All test case
97+
classes should extend from the basic abstract class `PHPCtagsTestCase` and be
98+
named as `t_<testcase_identifier>`. Only `$mExample` property must be assigned
99+
in the `__construt` function of the test case class. The other two properties
100+
`$mFormat` and `$mOptions` have default value predifined in the `__construct`
101+
function of the base `PHPCtagsTestCase` class which could be easily overwrited.
102+
103+
Here is the test case class for the previous example and should be stored in
104+
file `test_foo.testcase.php`.
105+
106+
```
107+
<?php
108+
class t_test_foo extends PHPCtagsTestCase {
109+
110+
public function __construct()
111+
{
112+
parent::__construct();
113+
$this->mExample = 'foo';
114+
}
115+
116+
}
117+
```
118+
119+
There is no naming restriction for `<example_identifier>` and
120+
`<testcase_identifier>`. Generally, the identifier for a bug fix reported in
121+
the issue list should be named as `bugfix_<issue number>`.
122+
123+
We are done now. Now the `PHPCtagsTest` class will automatically find these
124+
test cases and use them to feed the `testExport()` method by a means called
125+
__[Parameterized Test][]__.
126+
127+
[Parameterized Test]:http://xunitpatterns.com/Parameterized%20Test.html

0 commit comments

Comments
 (0)