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