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