3 use MediaWiki\Json\JsonCodec
;
4 use MediaWiki\Parser\ParserCache
;
5 use MediaWiki\Parser\ParserOptions
;
6 use MediaWiki\Parser\ParserOutput
;
7 use MediaWiki\PoolCounter\PoolWorkArticleViewCurrent
;
8 use MediaWiki\Revision\RevisionRecord
;
9 use MediaWiki\Status\Status
;
10 use Psr\Log\NullLogger
;
11 use Wikimedia\ObjectCache\HashBagOStuff
;
12 use Wikimedia\Rdbms\ChronologyProtector
;
13 use Wikimedia\Stats\StatsFactory
;
14 use Wikimedia\TestingAccessWrapper
;
17 * @covers \MediaWiki\PoolCounter\PoolWorkArticleViewCurrent
20 class PoolWorkArticleViewCurrentTest
extends PoolWorkArticleViewTest
{
22 /** @var ParserCache */
23 private $parserCache = null;
26 * @param WikiPage $page
27 * @param RevisionRecord|null $rev
28 * @param ParserOptions|null $options
30 * @return PoolWorkArticleViewCurrent
32 protected function newPoolWorkArticleView(
34 ?RevisionRecord
$rev = null,
38 $options = ParserOptions
::newFromAnon();
42 $rev = $page->getRevisionRecord();
45 $parserCache = $this->parserCache ?
: $this->installParserCache();
47 return new PoolWorkArticleViewCurrent(
48 'test:' . $rev->getId(),
52 $this->getServiceContainer()->getRevisionRenderer(),
54 $this->getServiceContainer()->getConnectionProvider(),
55 $this->getServiceContainer()->getChronologyProtector(),
56 $this->getLoggerSpi(),
57 $this->getServiceContainer()->getWikiPageFactory()
61 private function installParserCache( $bag = null ) {
62 $this->parserCache
= new ParserCache(
64 $bag ?
: new HashBagOStuff(),
66 $this->getServiceContainer()->getHookContainer(),
68 StatsFactory
::newNull(),
70 $this->getServiceContainer()->getTitleFactory(),
71 $this->getServiceContainer()->getWikiPageFactory(),
72 $this->getServiceContainer()->getGlobalIdGenerator()
75 return $this->parserCache
;
78 public function testUpdateCachedOutput() {
79 $options = ParserOptions
::newFromAnon();
80 $page = $this->getExistingTestPage( __METHOD__
);
82 $parserCache = $this->installParserCache();
84 // rendering of a deleted revision should work, audience checks are bypassed
85 $work = $this->newPoolWorkArticleView( $page, null, $options );
86 /** @var Status $status */
87 $status = $work->execute();
88 $this->assertStatusGood( $status );
90 $cachedOutput = $parserCache->get( $page, $options );
91 $this->assertNotEmpty( $cachedOutput );
92 $this->assertSame( $status->getValue()->getRawText(), $cachedOutput->getRawText() );
96 * Test that cache miss is not cached in-process, so pool work can fetch
97 * a parse cached by other pool work after waiting for a lock. See T277829
99 public function testFetchAfterMissWithLock() {
100 $bag = new HashBagOStuff();
101 $options = ParserOptions
::newFromAnon();
102 $page = $this->getExistingTestPage( __METHOD__
);
104 $this->installParserCache( $bag );
105 $work1 = $this->newPoolWorkArticleView( $page, null, $options );
106 $this->assertFalse( $work1->getCachedWork() );
108 // Pretend we're in another process with another ParserCache,
109 // but share the backend store
110 $this->installParserCache( $bag );
111 $work2 = $this->newPoolWorkArticleView( $page, null, $options );
112 /** @var Status $status2 */
113 $status2 = $work2->execute();
114 $this->assertStatusGood( $status2 );
116 // The parser output cached but $work2 should now be also visible to $work1
117 $status1 = $work1->getCachedWork();
118 $this->assertInstanceOf( ParserOutput
::class, $status1->getValue() );
119 $this->assertSame( $status2->getValue()->getText(), $status1->getValue()->getText() );
122 public function testFallbackFromOutdatedParserCache() {
123 // Fake Unix timestamps
125 $outdated = $lastWrite;
127 $chronologyProtector = $this->createNoOpMock( ChronologyProtector
::class, [ 'getTouched' ] );
128 $chronologyProtector->method( 'getTouched' )->willReturn( $lastWrite );
130 $output = $this->createNoOpMock( ParserOutput
::class, [ 'getCacheTime' ] );
131 $output->method( 'getCacheTime' )->willReturn( $outdated );
132 $this->parserCache
= $this->createNoOpMock( ParserCache
::class, [ 'getDirty' ] );
133 $this->parserCache
->method( 'getDirty' )->willReturn( $output );
135 $work = $this->newPoolWorkArticleView(
136 $this->createMock( WikiPage
::class ),
137 $this->createMock( RevisionRecord
::class )
139 TestingAccessWrapper
::newFromObject( $work )->chronologyProtector
= $chronologyProtector;
141 $this->assertFalse( $work->fallback( true ) );
143 $status = $work->fallback( false );
144 $this->assertInstanceOf( ParserOutput
::class, $status->getValue() );
145 $this->assertStatusWarning( 'view-pool-overload', $status );
148 public function testFallbackFromMoreRecentParserCache() {
149 // Fake Unix timestamps
151 $moreRecent = $lastWrite +
1;
153 $chronologyProtector = $this->createNoOpMock( ChronologyProtector
::class, [ 'getTouched' ] );
154 $chronologyProtector->method( 'getTouched' )->willReturn( $lastWrite );
156 $output = $this->createNoOpMock( ParserOutput
::class, [ 'getCacheTime' ] );
157 $output->method( 'getCacheTime' )->willReturn( $moreRecent );
158 $this->parserCache
= $this->createNoOpMock( ParserCache
::class, [ 'getDirty' ] );
159 $this->parserCache
->method( 'getDirty' )->willReturn( $output );
161 $work = $this->newPoolWorkArticleView(
162 $this->createMock( WikiPage
::class ),
163 $this->createMock( RevisionRecord
::class )
165 TestingAccessWrapper
::newFromObject( $work )->chronologyProtector
= $chronologyProtector;
167 $status = $work->fallback( true );
168 $this->assertInstanceOf( ParserOutput
::class, $status->getValue() );
169 $this->assertStatusWarning( 'view-pool-contention', $status );
171 $status = $work->fallback( false );
172 $this->assertInstanceOf( ParserOutput
::class, $status->getValue() );
173 $this->assertStatusWarning( 'view-pool-overload', $status );