Merge "Revert "Log the reason why revision->getContent() returns null""
[lhc/web/wiklou.git] / tests / phpunit / maintenance / fetchTextTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Maintenance;
4
5 use ContentHandler;
6 use FetchText;
7 use MediaWikiTestCase;
8 use MWException;
9 use Title;
10 use PHPUnit_Framework_ExpectationFailedException;
11 use WikiPage;
12
13 require_once __DIR__ . "/../../../maintenance/fetchText.php";
14
15 /**
16 * Mock for the input/output of FetchText
17 *
18 * FetchText internally tries to access stdin and stdout. We mock those aspects
19 * for testing.
20 */
21 class SemiMockedFetchText extends FetchText {
22
23 /**
24 * @var string|null Text to pass as stdin
25 */
26 private $mockStdinText = null;
27
28 /**
29 * @var bool Whether or not a text for stdin has been provided
30 */
31 private $mockSetUp = false;
32
33 /**
34 * @var array Invocation counters for the mocked aspects
35 */
36 private $mockInvocations = [ 'getStdin' => 0 ];
37
38 /**
39 * Data for the fake stdin
40 *
41 * @param string $stdin The string to be used instead of stdin
42 */
43 function mockStdin( $stdin ) {
44 $this->mockStdinText = $stdin;
45 $this->mockSetUp = true;
46 }
47
48 /**
49 * Gets invocation counters for mocked methods.
50 *
51 * @return array An array, whose keys are function names. The corresponding values
52 * denote the number of times the function has been invoked.
53 */
54 function mockGetInvocations() {
55 return $this->mockInvocations;
56 }
57
58 // -----------------------------------------------------------------
59 // Mocked functions from FetchText follow.
60
61 function getStdin( $len = null ) {
62 $this->mockInvocations['getStdin']++;
63 if ( $len !== null ) {
64 throw new PHPUnit_Framework_ExpectationFailedException(
65 "Tried to get stdin with non null parameter" );
66 }
67
68 if ( !$this->mockSetUp ) {
69 throw new PHPUnit_Framework_ExpectationFailedException(
70 "Tried to get stdin before setting up rerouting" );
71 }
72
73 return fopen( 'data://text/plain,' . $this->mockStdinText, 'r' );
74 }
75 }
76
77 /**
78 * TestCase for FetchText
79 *
80 * @group Database
81 * @group Dump
82 * @covers FetchText
83 */
84 class FetchTextTest extends MediaWikiTestCase {
85
86 // We add 5 Revisions for this test. Their corresponding text id's
87 // are stored in the following 5 variables.
88 protected static $textId1;
89 protected static $textId2;
90 protected static $textId3;
91 protected static $textId4;
92 protected static $textId5;
93
94 /**
95 * @var Exception|null As the current MediaWikiTestCase::run is not
96 * robust enough to recover from thrown exceptions directly, we cannot
97 * throw frow within addDBData, although it would be appropriate. Hence,
98 * we catch the exception and store it until we are in setUp and may
99 * finally rethrow the exception without crashing the test suite.
100 */
101 protected static $exceptionFromAddDBDataOnce;
102
103 /**
104 * @var FetchText The (mocked) FetchText that is to test
105 */
106 private $fetchText;
107
108 /**
109 * Adds a revision to a page, while returning the resuting text's id
110 *
111 * @param WikiPage $page The page to add the revision to
112 * @param string $text The revisions text
113 * @param string $summary The revisions summare
114 * @return int
115 * @throws MWException
116 */
117 private function addRevision( $page, $text, $summary ) {
118 $status = $page->doEditContent(
119 ContentHandler::makeContent( $text, $page->getTitle() ),
120 $summary
121 );
122
123 if ( $status->isGood() ) {
124 $value = $status->getValue();
125 $revision = $value['revision'];
126 $id = $revision->getTextId();
127
128 if ( $id > 0 ) {
129 return $id;
130 }
131 }
132
133 throw new MWException( "Could not determine text id" );
134 }
135
136 function addDBDataOnce() {
137 $wikitextNamespace = $this->getDefaultWikitextNS();
138
139 try {
140 $title = Title::newFromText( 'FetchTextTestPage1', $wikitextNamespace );
141 $page = WikiPage::factory( $title );
142 self::$textId1 = $this->addRevision(
143 $page,
144 "FetchTextTestPage1Text1",
145 "FetchTextTestPage1Summary1"
146 );
147
148 $title = Title::newFromText( 'FetchTextTestPage2', $wikitextNamespace );
149 $page = WikiPage::factory( $title );
150 self::$textId2 = $this->addRevision(
151 $page,
152 "FetchTextTestPage2Text1",
153 "FetchTextTestPage2Summary1"
154 );
155 self::$textId3 = $this->addRevision(
156 $page,
157 "FetchTextTestPage2Text2",
158 "FetchTextTestPage2Summary2"
159 );
160 self::$textId4 = $this->addRevision(
161 $page,
162 "FetchTextTestPage2Text3",
163 "FetchTextTestPage2Summary3"
164 );
165 self::$textId5 = $this->addRevision(
166 $page,
167 "FetchTextTestPage2Text4 some additional Text ",
168 "FetchTextTestPage2Summary4 extra "
169 );
170 } catch ( Exception $e ) {
171 // We'd love to pass $e directly. However, ... see
172 // documentation of exceptionFromAddDBDataOnce
173 self::$exceptionFromAddDBDataOnce = $e;
174 }
175 }
176
177 protected function setUp() {
178 parent::setUp();
179
180 // Check if any Exception is stored for rethrowing from addDBData
181 if ( self::$exceptionFromAddDBDataOnce !== null ) {
182 throw self::$exceptionFromAddDBDataOnce;
183 }
184
185 $this->fetchText = new SemiMockedFetchText();
186 }
187
188 /**
189 * Helper to relate FetchText's input and output
190 * @param string $input
191 * @param string $expectedOutput
192 */
193 private function assertFilter( $input, $expectedOutput ) {
194 $this->fetchText->mockStdin( $input );
195 $this->fetchText->execute();
196 $invocations = $this->fetchText->mockGetInvocations();
197 $this->assertEquals( 1, $invocations['getStdin'],
198 "getStdin invocation counter" );
199 $this->expectOutputString( $expectedOutput );
200 }
201
202 // Instead of the following functions, a data provider would be great.
203 // However, as data providers are evaluated /before/ addDBData, a data
204 // provider would not know the required ids.
205
206 function testExistingSimple() {
207 $this->assertFilter( self::$textId2,
208 self::$textId2 . "\n23\nFetchTextTestPage2Text1" );
209 }
210
211 function testExistingSimpleWithNewline() {
212 $this->assertFilter( self::$textId2 . "\n",
213 self::$textId2 . "\n23\nFetchTextTestPage2Text1" );
214 }
215
216 function testExistingSeveral() {
217 $this->assertFilter(
218 implode( "\n", [
219 self::$textId1,
220 self::$textId5,
221 self::$textId3,
222 self::$textId3,
223 ] ),
224 implode( '', [
225 self::$textId1 . "\n23\nFetchTextTestPage1Text1",
226 self::$textId5 . "\n44\nFetchTextTestPage2Text4 "
227 . "some additional Text",
228 self::$textId3 . "\n23\nFetchTextTestPage2Text2",
229 self::$textId3 . "\n23\nFetchTextTestPage2Text2"
230 ] ) );
231 }
232
233 function testEmpty() {
234 $this->assertFilter( "", null );
235 }
236
237 function testNonExisting() {
238 $this->assertFilter( self::$textId5 + 10, ( self::$textId5 + 10 ) . "\n-1\n" );
239 }
240
241 function testNegativeInteger() {
242 $this->assertFilter( "-42", "-42\n-1\n" );
243 }
244
245 function testFloatingPointNumberExisting() {
246 // float -> int -> revision
247 $this->assertFilter( self::$textId3 + 0.14159,
248 self::$textId3 . "\n23\nFetchTextTestPage2Text2" );
249 }
250
251 function testFloatingPointNumberNonExisting() {
252 $this->assertFilter( self::$textId5 + 3.14159,
253 ( self::$textId5 + 3 ) . "\n-1\n" );
254 }
255
256 function testCharacters() {
257 $this->assertFilter( "abc", "0\n-1\n" );
258 }
259
260 function testMix() {
261 $this->assertFilter( "ab\n" . self::$textId4 . ".5cd\n\nefg\n" . self::$textId2
262 . "\n" . self::$textId3,
263 implode( "", [
264 "0\n-1\n",
265 self::$textId4 . "\n23\nFetchTextTestPage2Text3",
266 "0\n-1\n",
267 "0\n-1\n",
268 self::$textId2 . "\n23\nFetchTextTestPage2Text1",
269 self::$textId3 . "\n23\nFetchTextTestPage2Text2"
270 ] ) );
271 }
272 }