Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5979269
HookContainerTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
35 KB
Referenced Files
None
Subscribers
None
HookContainerTest.php
View Options
<?php
namespace
MediaWiki\Tests\HookContainer
{
use
Error
;
use
InvalidArgumentException
;
use
LogicException
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\HookContainer\StaticHookRegistry
;
use
MediaWiki\Tests\Unit\DummyServicesTrait
;
use
MediaWikiUnitTestCase
;
use
stdClass
;
use
UnexpectedValueException
;
use
Wikimedia\ScopedCallback
;
use
Wikimedia\TestingAccessWrapper
;
class
HookContainerTest
extends
MediaWikiUnitTestCase
{
use
DummyServicesTrait
;
private
const
HANDLER_FUNCTION
=
FooClass
::
class
.
'::fooStaticMethod'
;
private
const
HANDLER_REGISTRATION
=
[
'extensionPath'
=>
__DIR__
,
'handler'
=>
[
'name'
=>
'TestHookHandler'
,
'class'
=>
'FooExtension
\H
ooks'
]
];
/**
* Creates a new hook container with StaticHookRegistry and empty ObjectFactory
*
* @param null|array $oldHooks
* @param null|array $newHooks
* @param array $deprecatedHooksArray
*
* @return HookContainer
*/
private
function
newHookContainer
(
$oldHooks
=
null
,
$newHooks
=
null
,
$deprecatedHooksArray
=
[]
)
{
if
(
$oldHooks
===
null
)
{
$oldHooks
[
'FoobarActionComplete'
][]
=
static
function
(
&
$called
)
{
$called
[]
=
11
;
};
}
if
(
$newHooks
===
null
)
{
$handler
=
[
'handler'
=>
[
'name'
=>
'FooExtension-FooActionHandler'
,
'class'
=>
'FooExtension
\\
Hooks'
,
'services'
=>
[]
]
];
$newHooks
=
[
'FooActionComplete'
=>
[
$handler
]
];
}
// fake object factory
$objectFactory
=
$this
->
getDummyObjectFactory
(
[
'SomeService'
=>
static
function
()
{
return
new
stdClass
();
}
]
);
$registry
=
new
StaticHookRegistry
(
$oldHooks
,
$newHooks
,
$deprecatedHooksArray
);
$hookContainer
=
new
HookContainer
(
$registry
,
$objectFactory
);
return
$hookContainer
;
}
public
static
function
provideRegister
()
{
return
[
'function'
=>
[
'strtoupper'
,
'strtoupper'
],
'object'
=>
[
new
\FooExtension\Hooks
(),
'FooExtension
\H
ooks::onFooActionComplete'
],
'object and method'
=>
[
[
new
FooClass
(),
'fooMethod'
],
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooMethod'
],
'extension'
=>
[
self
::
HANDLER_REGISTRATION
,
'FooExtension
\H
ooks::onFooActionComplete'
],
'callable referencing a class that extends an unknown class'
=>
[
[
'MediaWiki
\T
ests
\B
rokenClass'
,
'aMethod'
],
'MediaWiki
\T
ests
\B
rokenClass::aMethod'
],
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::register
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRegister
*/
public
function
testRegister
(
$handler
,
$expected
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[
'FooActionComplete'
=>
[
$handler
]
],
[]
);
$handlers
=
$hookContainer
->
getHandlerDescriptions
(
'FooActionComplete'
);
$this
->
assertSame
(
$expected
,
$handlers
[
0
]
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::getHandlerDescriptions
*/
public
function
testGetHandlerDescriptions
()
{
$handler
=
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooStaticMethod'
;
$expected
=
[
$handler
];
$hookContainer
=
$this
->
newHookContainer
(
[
'BarActionComplete'
=>
[
$handler
]
],
[
'FooActionComplete'
=>
[
$handler
]
],
[]
);
$this
->
assertSame
(
$expected
,
$hookContainer
->
getHandlerDescriptions
(
'FooActionComplete'
)
);
$this
->
assertSame
(
$expected
,
$hookContainer
->
getHandlerDescriptions
(
'BarActionComplete'
)
);
// Fire the hooks, then check again
$hookContainer
->
run
(
'FooActionComplete'
,
[
1
]
);
$hookContainer
->
run
(
'BarActionComplete'
,
[
1
]
);
$this
->
assertSame
(
$expected
,
$hookContainer
->
getHandlerDescriptions
(
'FooActionComplete'
)
);
$this
->
assertSame
(
$expected
,
$hookContainer
->
getHandlerDescriptions
(
'BarActionComplete'
)
);
}
/**
* Values returned: hook, handlersToRegister, expectedReturn
*/
public
static
function
provideGetHandlerDescriptions
()
{
return
[
'NoHandlersExist'
=>
[
'MWTestHook'
,
null
,
0
,
0
],
'SuccessfulHandlerReturn'
=>
[
'FooActionComplete'
,
[
'handler'
=>
[
'name'
=>
'FooExtension-FooActionHandler'
,
'class'
=>
'FooExtension
\\
Hooks'
,
'services'
=>
[],
],
],
1
,
1
],
'SkipDeprecated'
=>
[
'FooActionCompleteDeprecated'
,
[
'handler'
=>
[
'name'
=>
'FooExtension-FooActionHandler'
,
'class'
=>
'FooExtension
\\
Hooks'
,
'services'
=>
[],
],
'deprecated'
=>
true
,
],
1
,
0
],
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::salvage
*/
public
function
testSalvage
()
{
$firstHookContainer
=
$this
->
newHookContainer
(
[],
[]
);
$secondHookContainer
=
$this
->
newHookContainer
();
$firstHookContainer
->
register
(
'TestHook'
,
self
::
HANDLER_FUNCTION
);
$secondHookContainer
->
salvage
(
$firstHookContainer
);
$this
->
assertTrue
(
$secondHookContainer
->
isRegistered
(
'TestHook'
)
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::salvage
*/
public
function
testSalvageThrows
()
{
$firstHookContainer
=
$this
->
newHookContainer
(
[],
[]
);
$secondHookContainer
=
$this
->
newHookContainer
();
$secondHookContainer
->
register
(
'TestHook'
,
self
::
HANDLER_FUNCTION
);
$this
->
expectException
(
LogicException
::
class
);
$secondHookContainer
->
salvage
(
$firstHookContainer
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::isRegistered
* @covers \MediaWiki\HookContainer\HookContainer::register
* @covers \MediaWiki\HookContainer\HookContainer::clear
*/
public
function
testIsRegistered
()
{
$hookContainer
=
$this
->
newHookContainer
(
[
'XyzHook'
=>
[
self
::
HANDLER_FUNCTION
]
],
[
'MWTestHook'
=>
[
self
::
HANDLER_REGISTRATION
]
],
);
$hookContainer
->
register
(
'AbcHook'
,
self
::
HANDLER_FUNCTION
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'XyzzyHook'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'XyzHook'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'MWTestHook'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'AbcHook'
)
);
$hookContainer
->
clear
(
'AbcHook'
);
$hookContainer
->
clear
(
'XyzHook'
);
$hookContainer
->
clear
(
'MWTestHook'
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'XyzHook'
)
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'MWTestHook'
)
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'AbcHook'
)
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::scopedRegister
*/
public
function
testScopedRegister
()
{
$hookContainer
=
$this
->
newHookContainer
();
$reset
=
$hookContainer
->
scopedRegister
(
'MWTestHook'
,
[
new
FooClass
(),
'fooMethod'
]
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'MWTestHook'
)
);
ScopedCallback
::
consume
(
$reset
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'MWTestHook'
)
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::scopedRegister
*/
public
function
testScopedRegisterTwoHandlers
()
{
$hookContainer
=
$this
->
newHookContainer
();
$called1
=
$called2
=
false
;
$reset1
=
$hookContainer
->
scopedRegister
(
'MWTestHook'
,
static
function
()
use
(
&
$called1
)
{
$called1
=
true
;
}
);
$reset2
=
$hookContainer
->
scopedRegister
(
'MWTestHook'
,
static
function
()
use
(
&
$called2
)
{
$called2
=
true
;
}
);
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
assertTrue
(
$called1
);
$this
->
assertTrue
(
$called2
);
$called1
=
$called2
=
false
;
ScopedCallback
::
consume
(
$reset1
);
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
assertFalse
(
$called1
);
$this
->
assertTrue
(
$called2
);
$called1
=
$called2
=
false
;
ScopedCallback
::
consume
(
$reset2
);
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
assertFalse
(
$called1
);
$this
->
assertFalse
(
$called2
);
}
/**
* Register handlers with scopedRegister() and register()
* @covers \MediaWiki\HookContainer\HookContainer::scopedRegister
*/
public
function
testHandlersRegisteredWithScopedRegisterAndRegister
()
{
$hookContainer
=
$this
->
newHookContainer
();
$numCalls
=
0
;
$hookContainer
->
register
(
'MWTestHook'
,
static
function
()
use
(
&
$numCalls
)
{
$numCalls
++;
}
);
$reset
=
$hookContainer
->
scopedRegister
(
'MWTestHook'
,
static
function
()
use
(
&
$numCalls
)
{
$numCalls
++;
}
);
// handlers registered in 2 different ways
$this
->
assertCount
(
2
,
$hookContainer
->
getHandlerDescriptions
(
'MWTestHook'
)
);
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
assertEquals
(
2
,
$numCalls
);
// Remove one of the handlers that increments $called
ScopedCallback
::
consume
(
$reset
);
$this
->
assertCount
(
1
,
$hookContainer
->
getHandlerDescriptions
(
'MWTestHook'
)
);
$numCalls
=
0
;
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
assertSame
(
1
,
$numCalls
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::getHandlerDescriptions
* @covers \MediaWiki\HookContainer\HookContainer::getHandlerCallbacks
* @dataProvider provideGetHandlerDescriptions
*/
public
function
testGetHandlers
(
string
$hook
,
?
array
$handlerToRegister
,
int
$expectedDescriptions
,
int
$expectedCallbacks
)
{
if
(
$handlerToRegister
)
{
$hooks
=
[
$hook
=>
[
$handlerToRegister
]
];
}
else
{
$hooks
=
[];
}
$fakeDeprecatedHooks
=
[
'FooActionCompleteDeprecated'
=>
[
'deprecatedVersion'
=>
'1.35'
]
];
$hookContainer
=
$this
->
newHookContainer
(
[],
$hooks
,
$fakeDeprecatedHooks
);
$descriptions
=
$hookContainer
->
getHandlerDescriptions
(
$hook
);
$this
->
assertCount
(
$expectedDescriptions
,
$descriptions
,
'getHandlerDescriptions()'
);
$this
->
expectDeprecationAndContinue
(
'/getHandlerCallbacks/'
);
$callbacks
=
$hookContainer
->
getHandlerCallbacks
(
$hook
);
$this
->
assertCount
(
$expectedCallbacks
,
$callbacks
,
'getHandlerCallbacks()'
);
foreach
(
$callbacks
as
$clbk
)
{
$this
->
assertIsCallable
(
$clbk
);
}
}
public
static
function
provideRunConfigured
()
{
$fooObj
=
new
FooClass
();
$closure
=
static
function
(
&
$count
)
{
$count
++;
};
$extra
=
10
;
return
[
// Callables
'Function'
=>
[
'fooGlobalFunction'
],
'Object and method'
=>
[
[
$fooObj
,
'fooMethod'
]
],
'Class name and static method'
=>
[
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass'
,
'fooStaticMethod'
]
],
'static method'
=>
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooStaticMethod'
],
'Closure'
=>
[
$closure
],
// Shorthand
'Object'
=>
[
$fooObj
],
// No-ops
'NOOP'
=>
[
HookContainer
::
NOOP
,
1
],
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunConfigured
*/
public
function
testRunConfigured
(
$handler
,
$expectedCount
=
2
)
{
$hookContainer
=
$this
->
newHookContainer
(
[
'Increment'
=>
[
$handler
]
]
);
$count
=
1
;
$hookValue
=
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertTrue
(
$hookValue
);
$this
->
assertSame
(
$expectedCount
,
$count
);
}
public
static
function
provideRunDeprecatedStyle
()
{
$fooObj
=
new
FooClass
();
$closure
=
static
function
(
&
$count
)
{
$count
++;
};
$extra
=
10
;
return
[
// Handlers with extra data attached
'static method with extra data'
=>
[
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooStaticMethodWithExtra'
,
$extra
],
11
],
'Object and method with extra data'
=>
[
[
[
$fooObj
,
'fooMethodWithExtra'
],
$extra
],
11
],
'Function extra data'
=>
[
[
'fooGlobalFunctionWithExtra'
,
$extra
],
11
],
'Closure with extra data'
=>
[
[
static
function
(
int
$inc
,
&
$count
)
{
$count
+=
$inc
;
},
10
],
11
],
// No-ops
'empty array'
=>
[
[],
1
],
'null'
=>
[
null
,
1
],
'false'
=>
[
false
,
1
],
// Strange edge cases
'Object in array without method'
=>
[
[
$fooObj
]
],
'Callable in array'
=>
[
[
[
$fooObj
,
'fooMethod'
]
]
],
'Closure in array with no extra data'
=>
[
[
$closure
]
],
'Function in array'
=>
[
[
'fooGlobalFunction'
]
],
'Function in array in array'
=>
[
[
[
'fooGlobalFunction'
]
]
],
'static method as array in array'
=>
[
[
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass'
,
'fooStaticMethod'
]
]
],
'Object and fully-qualified non-static method'
=>
[
[
$fooObj
,
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooMethod'
]
]
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunDeprecatedStyle
*/
public
function
testRunDeprecatedStyle
(
$handler
,
$expectedCount
=
2
)
{
$hookContainer
=
$this
->
newHookContainer
(
[
'Increment'
=>
[
$handler
]
]
);
$this
->
expectDeprecationAndContinue
(
'/Deprecated handler style/'
);
$count
=
1
;
$hookValue
=
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertTrue
(
$hookValue
);
$this
->
assertSame
(
$expectedCount
,
$count
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunConfigured
* @dataProvider provideRunExtensionHook
*/
public
function
testRegisterAndRun
(
$handler
,
$expectedCount
=
2
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[]
);
$hookContainer
->
register
(
'Increment'
,
$handler
);
$count
=
1
;
$hookValue
=
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertTrue
(
$hookValue
);
$this
->
assertSame
(
$expectedCount
,
$count
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunDeprecatedStyle
*/
public
function
testRegisterDeprecatedStyle
(
$handler
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[]
);
// Force the handler list to be initialized, so register() will normalize the handler immediately.
$hookContainer
->
run
(
'Increment'
);
$this
->
expectDeprecationAndContinue
(
'/Deprecated handler style for hook/'
);
$hookContainer
->
register
(
'Increment'
,
$handler
);
}
/**
* Values returned: hook, handler, handler arguments, options
*/
public
static
function
provideRegisterAndRunCallback
()
{
$fooObj
=
new
FooClass
();
return
[
// Callables
'Function'
=>
[
'fooGlobalFunction'
],
'Object and method'
=>
[
[
$fooObj
,
'fooMethod'
]
],
'Class name and static method'
=>
[
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass'
,
'fooStaticMethod'
]
],
'static method'
=>
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooStaticMethod'
],
'Closure'
=>
[
static
function
(
&
$count
)
{
$count
++;
}
],
// Extension-style handler
'Extension handler'
=>
[
self
::
HANDLER_REGISTRATION
],
// NOTE: hook handlers with extra data are not supported for callbacks!
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::getHandlerCallbacks
* @dataProvider provideRegisterAndRunCallback
*/
public
function
testRegisterAndRunCallback
(
$handler
,
$expectedCount
=
2
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[]
);
$hookContainer
->
register
(
'Increment'
,
$handler
);
$this
->
expectDeprecationAndContinue
(
'/getHandlerCallbacks/'
);
$count
=
1
;
foreach
(
$hookContainer
->
getHandlerCallbacks
(
'Increment'
)
as
$callback
)
{
$callback
(
$count
);
}
$this
->
assertSame
(
$expectedCount
,
$count
);
}
/**
* Values returned: hook, handler, handler arguments, options
*/
public
static
function
provideRunExtensionHook
()
{
return
[
[
self
::
HANDLER_REGISTRATION
],
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunExtensionHook
*/
public
function
testRunExtensionHook
(
array
$handler
,
$expectedCount
=
1
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[
'X
\\
Y::Increment'
=>
[
$handler
]
]
);
$count
=
0
;
$hookValue
=
$hookContainer
->
run
(
'X
\\
Y::Increment'
,
[
&
$count
]
);
$this
->
assertTrue
(
$hookValue
);
$this
->
assertSame
(
$expectedCount
,
$count
);
}
public
static
function
provideRunFailsWithNoService
()
{
$handler
=
self
::
HANDLER_REGISTRATION
;
$handler
[
'handler'
][
'services'
]
=
[
'SomeService'
];
yield
[
$handler
];
$handler
=
self
::
HANDLER_REGISTRATION
;
$handler
[
'handler'
][
'optional_services'
]
=
[
'SomeService'
];
yield
[
$handler
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @dataProvider provideRunFailsWithNoService
*/
public
function
testRunFailsWithNoService
(
array
$handler
)
{
$hookContainer
=
$this
->
newHookContainer
(
[],
[
'Increment'
=>
[
$handler
]
]
);
$this
->
expectException
(
UnexpectedValueException
::
class
);
$count
=
0
;
$options
=
[
'noServices'
=>
true
];
$hookContainer
->
run
(
'Increment'
,
[
&
$count
],
$options
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
*/
public
function
testRunOrder
()
{
$configured1
=
static
function
(
&
$seq
)
{
$seq
[]
=
'configured1'
;
};
$configured2
=
static
function
(
&
$seq
)
{
$seq
[]
=
'configured2'
;
};
$registered
=
static
function
(
&
$seq
)
{
$seq
[]
=
'registered'
;
};
$hookContainer
=
$this
->
newHookContainer
(
[
'Append'
=>
[
$configured1
,
$configured2
]
],
[
'Append'
=>
[
self
::
HANDLER_REGISTRATION
]
]
);
$hookContainer
->
register
(
'Append'
,
$registered
);
$seq
=
[
'start'
];
$hookContainer
->
run
(
'Append'
,
[
&
$seq
]
);
$expected
=
[
'start'
,
'configured1'
,
'configured2'
,
'FooExtension'
,
'registered'
];
$this
->
assertSame
(
$expected
,
$seq
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::run
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* Test HookContainer::run() when the handler returns false
*/
public
function
testRunAbort
()
{
$handler1
=
[
'handler'
=>
[
'name'
=>
'FooExtension-Abort1'
,
'class'
=>
'FooExtension
\\
AbortHooks1'
]
];
$handler2
=
[
'handler'
=>
[
'name'
=>
'FooExtension-Abort2'
,
'class'
=>
'FooExtension
\\
AbortHooks2'
]
];
$handler3
=
[
'handler'
=>
[
'name'
=>
'FooExtension-Abort3'
,
'class'
=>
'FooExtension
\\
AbortHooks3'
]
];
$hooks
=
[
'Abort'
=>
[
$handler1
,
$handler2
,
$handler3
]
];
$hookContainer
=
$this
->
newHookContainer
(
[],
$hooks
);
$called
=
[];
$ret
=
$hookContainer
->
run
(
'Abort'
,
[
&
$called
]
);
$this
->
assertFalse
(
$ret
);
$this
->
assertArrayEquals
(
[
1
,
2
],
$called
);
}
public
static
function
provideRegisterDeprecated
()
{
// registering a deprecated hook should trigger a warning
yield
[
[
'deprecatedVersion'
=>
'1.0'
],
true
];
// the silent flag should suppress the warning
yield
[
[
'deprecatedVersion'
=>
'1.0'
,
'silent'
=>
true
],
false
];
}
/**
* Test HookContainer::register() successfully registers even when hook is deprecated.
* @covers \MediaWiki\HookContainer\HookContainer::register
* @dataProvider provideRegisterDeprecated
*/
public
function
testRegisterDeprecated
(
array
$deprecationInfo
,
bool
$expectWarning
)
{
$deprecations
=
[
'FooActionComplete'
=>
$deprecationInfo
];
// Assert we don't get any deprecation warnings during initialization!
$this
->
newHookContainer
(
[
'FooActionComplete'
=>
[
self
::
HANDLER_FUNCTION
]
],
[
'FooActionComplete'
=>
[
self
::
HANDLER_REGISTRATION
]
],
$deprecations
);
// Make a hook container with no hooks registered yet
$hookContainer
=
$this
->
newHookContainer
(
[],
[],
$deprecations
);
// Expected deprecation?
if
(
$expectWarning
)
{
$this
->
expectDeprecationAndContinue
(
'/FooActionComplete hook/'
);
}
$hookContainer
->
register
(
'FooActionComplete'
,
self
::
HANDLER_FUNCTION
);
// Deprecated hooks should still be functional!
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'FooActionComplete'
)
);
}
/**
* Test running deprecated hooks from $wgHooks with the deprecation declared in HookContainer.
* @covers \MediaWiki\HookContainer\HookContainer::run
* @dataProvider provideRegisterDeprecated
*/
public
function
testRunConfiguredDeprecated
(
array
$deprecationInfo
)
{
$hookContainer
=
$this
->
newHookContainer
(
[
'Increment'
=>
[
self
::
HANDLER_FUNCTION
]
],
[],
[
'Increment'
=>
$deprecationInfo
]
);
// No warning expected when running the hook!
// Deprecated hooks should still be functional!
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertSame
(
1
,
$count
);
}
/**
* Test running deprecated hooks from $wgHooks with the deprecation passed in the options parameter.
* @covers \MediaWiki\HookContainer\HookContainer::run
* @dataProvider provideRegisterDeprecated
*/
public
function
testRunConfiguredDeprecatedWithOption
(
array
$deprecationInfo
,
bool
$expectWarning
)
{
// Assert we don't get any deprecation warnings during initialization!
$hookContainer
=
$this
->
newHookContainer
(
[
'Increment'
=>
[
self
::
HANDLER_FUNCTION
]
],
);
// Expected deprecation?
if
(
$expectWarning
)
{
$this
->
expectDeprecationAndContinue
(
'/Use of Increment hook/'
);
}
// Deprecated hooks should still be functional!
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
],
$deprecationInfo
);
$this
->
assertSame
(
1
,
$count
);
}
/**
* Test running deprecated hooks from extensions.
*
* @covers \MediaWiki\HookContainer\HookContainer::run
*/
public
function
testRunHandlerObjectDeprecated
()
{
$deprecationInfo
=
[
'deprecatedVersion'
=>
'1.0'
];
// If the handler acknowledges deprecation, it should be skipped
$knownDeprecated
=
self
::
HANDLER_REGISTRATION
+
[
'deprecated'
=>
true
];
$hookContainer
=
$this
->
newHookContainer
(
[],
[
'Increment'
=>
[
self
::
HANDLER_REGISTRATION
,
$knownDeprecated
]
],
[
'Increment'
=>
$deprecationInfo
]
);
// Deprecated hooks should be functional, the handle that acknowledges deprecation should be skipped.
// We do not expect deprecation warnings here. They are covered by emitDeprecationWarnings()
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertSame
(
1
,
$count
);
}
/**
* Test running deprecated hooks from extensions.
*
* @covers \MediaWiki\HookContainer\HookContainer::run
*/
public
function
testRunHandlerObjectDeprecatedWithOption
()
{
$deprecationInfo
=
[
'deprecatedVersion'
=>
'1.0'
];
// If the handler acknowledges deprecation, it should be skipped
$knownDeprecated
=
self
::
HANDLER_REGISTRATION
+
[
'deprecated'
=>
true
];
$hookContainer
=
$this
->
newHookContainer
(
[],
[
'Increment'
=>
[
self
::
HANDLER_REGISTRATION
,
$knownDeprecated
]
],
[
'Increment'
=>
$deprecationInfo
]
);
// We do expect deprecation warnings when the 'deprecationVersion' key is provided in the $options parameter.
$this
->
expectDeprecationAndContinue
(
'/Use of Increment hook/'
);
// Deprecated hooks should be functional, the handle that acknowledges deprecation should be skipped.
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
],
$deprecationInfo
);
$this
->
assertSame
(
1
,
$count
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::getHookNames
*/
public
function
testGetHookNames
()
{
$fooHandler
=
[
'handler'
=>
[
'name'
=>
'FooHookHandler'
,
'class'
=>
'FooExtension
\\
Hooks'
]
];
$noop
=
static
function
()
{
// noop
};
$container
=
$this
->
newHookContainer
(
[
'A'
=>
[
$noop
]
],
[
'B'
=>
[
$fooHandler
]
]
);
$container
->
register
(
'C'
,
'strtoupper'
);
// Ask for a few hooks that have no handlers.
// Negative caching inside HookHandler should not cause them to be returned from getHookNames
$container
->
isRegistered
(
'X'
);
$this
->
expectDeprecationAndContinue
(
'/getHandlerCallbacks/'
);
$container
->
getHandlerCallbacks
(
'Y'
);
$this
->
assertArrayEquals
(
[
'A'
,
'B'
,
'C'
],
$container
->
getHookNames
()
);
// make sure we are getting each hook name only once
$container
->
register
(
'B'
,
'strtoupper'
);
$container
->
register
(
'A'
,
'strtoupper'
);
$this
->
assertArrayEquals
(
[
'A'
,
'B'
,
'C'
],
$container
->
getHookNames
()
);
}
/**
* Values returned: hook, handlersToRegister, options
*/
public
static
function
provideRunErrors
()
{
// XXX: should also fail: non-function string, empty array
return
[
'return a string'
=>
[
static
function
()
{
return
'string'
;
},
[]
],
'abort even though not abortable'
=>
[
static
function
()
{
return
false
;
},
[
'abortable'
=>
false
]
],
'callable referencing a class that extends an unknown class'
=>
[
[
'MediaWiki
\\
Tests
\\
BrokenClass2'
,
'aMethod'
],
[],
Error
::
class
],
];
}
/**
* @dataProvider provideRunErrors
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* Test errors thrown with invalid handlers
*/
public
function
testRunErrors
(
$handler
,
$options
,
$expected
=
UnexpectedValueException
::
class
)
{
$hookContainer
=
$this
->
newHookContainer
();
$hookContainer
->
register
(
'MWTestHook'
,
$handler
);
$this
->
filterDeprecated
(
'/^Returning a string from a hook handler/'
);
$this
->
expectException
(
$expected
);
$hookContainer
->
run
(
'MWTestHook'
,
[],
$options
);
}
/**
* Values returned: hook, handlersToRegister, options
*/
public
static
function
provideRegisterErrors
()
{
// XXX: should also fail: non-function string, empty array
return
[
'a number'
=>
[
123
],
'non-callable string'
=>
[
'a, b, c'
],
'array referencing an unknown method'
=>
[
[
self
::
class
,
'thisMethodDoesNotExist'
]
],
'empty string'
=>
[
''
],
'zero'
=>
[
0
],
'true'
=>
[
true
],
'callable referencing an unknown class'
=>
[
[
'FooExtension
\D
oesNotExist'
,
'onFoo'
],
'FooExtension
\D
oesNotExist::onFoo'
],
];
}
/**
* @dataProvider provideRegisterErrors
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @covers \MediaWiki\HookContainer\HookContainer::register
*/
public
function
testRegisterErrors
(
$badHandler
)
{
$hookContainer
=
$this
->
newHookContainer
();
// Force the handler list to be initialized, so register() will normalize the handler immediately.
$hookContainer
->
run
(
'MWTestHook'
);
$this
->
expectException
(
InvalidArgumentException
::
class
);
$hookContainer
->
register
(
'MWTestHook'
,
$badHandler
);
}
/**
* @dataProvider provideRegisterErrors
* @covers \MediaWiki\HookContainer\HookContainer::normalizeHandler
* @covers \MediaWiki\HookContainer\HookContainer::run
*/
public
function
testRunWithBadHandlers
(
$badHandler
)
{
$goodHandler
=
self
::
HANDLER_FUNCTION
;
$hookContainer
=
$this
->
newHookContainer
(
[
'MWTestHook'
=>
[
$badHandler
,
$goodHandler
]
]
);
// Bad handlers from the constructor should fail silently
$count
=
0
;
$hookContainer
->
run
(
'MWTestHook'
,
[
&
$count
]
);
$this
->
assertSame
(
1
,
$count
);
}
public
static
function
provideEmitDeprecationWarnings
()
{
yield
'Deprecated extension hook'
=>
[
'$oldHooks'
=>
[],
'$newHooks'
=>
[
self
::
HANDLER_REGISTRATION
],
'$deprecationInfo'
=>
[
'deprecatedVersion'
=>
'1.35'
],
'$expectWarning'
=>
true
,
];
yield
'Deprecated extension hook, silent'
=>
[
'$oldHooks'
=>
[],
'$newHooks'
=>
[
self
::
HANDLER_REGISTRATION
],
'$deprecationInfo'
=>
[
'deprecatedVersion'
=>
'1.35'
,
'silent'
=>
true
],
'$expectWarning'
=>
false
,
];
yield
'Deprecated extension hook, acknowledged'
=>
[
'$oldHooks'
=>
[],
'$newHooks'
=>
[
self
::
HANDLER_REGISTRATION
+
[
'deprecated'
=>
true
]
],
'$deprecationInfo'
=>
[
'deprecatedVersion'
=>
'1.35'
],
'$expectWarning'
=>
false
,
];
yield
'Deprecated configured hook'
=>
[
'$oldHooks'
=>
[
self
::
HANDLER_FUNCTION
],
'$newHooks'
=>
[],
'$deprecationInfo'
=>
[
'deprecatedVersion'
=>
'1.35'
],
'$expectWarning'
=>
false
,
// NOTE: Currently expected to be ignored. This may change.
];
yield
'Deprecated configured hook, silent'
=>
[
'$oldHooks'
=>
[
self
::
HANDLER_FUNCTION
],
'$newHooks'
=>
[],
'$deprecationInfo'
=>
[
'deprecatedVersion'
=>
'1.35'
,
'silent'
=>
true
],
'$expectWarning'
=>
false
,
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::emitDeprecationWarnings
* @dataProvider provideEmitDeprecationWarnings
*/
public
function
testEmitDeprecationWarnings
(
$oldHandlers
,
$newHandlers
,
$deprecationInfo
,
$expectWarning
)
{
$hookContainer
=
$this
->
newHookContainer
(
[
'FooActionComplete'
=>
$oldHandlers
],
[
'FooActionComplete'
=>
$newHandlers
],
[
'FooActionComplete'
=>
$deprecationInfo
]
);
if
(
$expectWarning
)
{
$this
->
expectDeprecationAndContinue
(
'/Hook FooActionComplete was deprecated/'
);
}
$hookContainer
->
emitDeprecationWarnings
();
$this
->
addToAssertionCount
(
1
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::emitDeprecationWarnings
*/
public
function
testEmitDeprecationWarningsSilent
()
{
$hooks
=
[
'FooActionComplete'
=>
[
[
'handler'
=>
'fooGlobalFunction'
,
'extensionPath'
=>
'fake-extension.json'
]
]
];
$deprecatedHooksArray
=
[
'FooActionComplete'
=>
[
'deprecatedVersion'
=>
'1.35'
,
'silent'
=>
true
]
];
$hookContainer
=
$this
->
newHookContainer
(
[],
$hooks
,
$deprecatedHooksArray
);
$hookContainer
->
emitDeprecationWarnings
();
$this
->
assertTrue
(
true
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::isRegistered
* @covers \MediaWiki\HookContainer\HookContainer::getHandlerCallbacks
* @covers \MediaWiki\HookContainer\HookContainer::register
* @covers \MediaWiki\HookContainer\HookContainer::clear
*/
public
function
testClear
()
{
$increment
=
[
new
\FooExtension\Hooks
(),
'onIncrement'
];
$hookContainer
=
$this
->
newHookContainer
(
[
'Increment'
=>
[
$increment
],
'XyzHook'
=>
[
self
::
HANDLER_FUNCTION
],
],
[
'Increment'
=>
[
self
::
HANDLER_REGISTRATION
],
'FooActionComplete'
=>
[
self
::
HANDLER_REGISTRATION
]
],
);
$hookContainer
->
register
(
'AbcHook'
,
self
::
HANDLER_FUNCTION
);
$hookContainer
->
register
(
'Increment'
,
static
function
(
&
$count
)
{
$count
++;
}
);
// Check: all three handlers should be called initially.
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertSame
(
3
,
$count
);
$hookContainer
->
clear
(
'Increment'
);
$this
->
assertFalse
(
$hookContainer
->
isRegistered
(
'Increment'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'AbcHook'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'XyzHook'
)
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'FooActionComplete'
)
);
$this
->
assertCount
(
0
,
$hookContainer
->
getHandlerDescriptions
(
'Increment'
)
);
$this
->
assertNotEmpty
(
$hookContainer
->
getHandlerDescriptions
(
'AbcHook'
)
);
$this
->
assertNotEmpty
(
$hookContainer
->
getHandlerDescriptions
(
'FooActionComplete'
)
);
$this
->
assertNotEmpty
(
$hookContainer
->
getHandlerDescriptions
(
'XyzHook'
)
);
// No more increment!
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertSame
(
3
,
$count
);
// When adding a handler again...
$hookContainer
->
register
(
'Increment'
,
static
function
(
&
$count
)
{
$count
=
11
;
}
);
// ...the new handler should be called, but not the old ones.
$count
=
0
;
$hookContainer
->
run
(
'Increment'
,
[
&
$count
]
);
$this
->
assertSame
(
11
,
$count
);
$this
->
assertTrue
(
$hookContainer
->
isRegistered
(
'Increment'
)
);
}
public
static
function
provideMayBeCallable
()
{
yield
'function'
=>
[
'strtoupper'
,
];
yield
'closure'
=>
[
static
function
()
{
// noop
},
];
yield
'object and method'
=>
[
[
new
FooClass
(),
'fooMethod'
],
];
yield
'static method as array'
=>
[
[
FooClass
::
class
,
'fooStaticMethod'
,
],
];
yield
'static method as string'
=>
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::fooStaticMethod'
,
];
yield
'callable referencing a class that extends an unknown class'
=>
[
[
'MediaWiki
\T
ests
\B
rokenClass3'
,
'aMethod'
],
];
}
public
static
function
provideNotCallable
()
{
yield
'object'
=>
[
new
\FooExtension\Hooks
(),
];
yield
'object and non-existing method'
=>
[
[
new
FooClass
(),
'noSuchMethod'
],
];
yield
'object and method and extra stuff'
=>
[
[
new
FooClass
(),
'fooMethod'
,
'extra'
,
'stuff'
],
];
yield
'object and method assoc'
=>
[
[
'a'
=>
new
FooClass
(),
'b'
=>
'fooMethod'
],
];
yield
'object and method nested in array'
=>
[
[
[
new
FooClass
(),
'fooMethod'
],
'whatever'
],
];
yield
'non-existing static method on existing class'
=>
[
'MediaWiki
\T
ests
\H
ookContainer
\F
ooClass::noSuchMethod'
,
];
yield
'global function with extra data in array'
=>
[
[
'strtoupper'
,
'extra'
],
];
yield
'non-existing static method on existing class as array'
=>
[
[
FooClass
::
class
,
'noSuchMethod'
],
];
yield
'non-function text'
=>
[
'just some text'
,
];
yield
'object in array with no method'
=>
[
[
new
\FooExtension\Hooks
()
],
];
yield
'callable referencing an unknown class'
=>
[
[
'FooExtension
\D
oesNotExist'
,
'onFoo'
],
];
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::mayBeCallable
* @dataProvider provideMayBeCallable
*/
public
function
testMayBeCallable_true
(
$v
)
{
$access
=
TestingAccessWrapper
::
newFromClass
(
HookContainer
::
class
);
$this
->
assertTrue
(
$access
->
mayBeCallable
(
$v
)
);
}
/**
* @covers \MediaWiki\HookContainer\HookContainer::mayBeCallable
* @dataProvider provideNotCallable
*/
public
function
testMayBeCallable_false
(
$v
)
{
$access
=
TestingAccessWrapper
::
newFromClass
(
HookContainer
::
class
);
$this
->
assertFalse
(
$access
->
mayBeCallable
(
$v
)
);
}
}
// Mock class for different types of handler functions
class
FooClass
{
public
function
fooMethod
(
&
$count
)
{
$count
++;
return
true
;
}
public
function
onIncrement
(
&
$count
)
{
$count
++;
}
public
static
function
fooStaticMethod
(
&
$count
)
{
$count
++;
return
null
;
}
public
function
fooMethodWithExtra
(
int
$inc
,
&
$count
)
{
$count
+=
$inc
;
return
true
;
}
public
static
function
fooStaticMethodWithExtra
(
int
$inc
,
&
$count
)
{
$count
+=
$inc
;
return
null
;
}
public
static
function
fooMethodReturnValueError
()
{
return
'a string'
;
}
public
static
function
onMWTestHook
()
{
// noop
}
}
}
// Function in global namespace
namespace
{
function
fooGlobalFunction
(
&
$count
)
{
$count
++;
return
true
;
}
function
fooGlobalFunctionWithExtra
(
$inc
,
&
$count
)
{
$count
+=
$inc
;
}
}
// Mock Extension
namespace
FooExtension
{
class
Hooks
{
public
function
onFooActionComplete
()
{
return
true
;
}
public
function
onMWTest
()
{
// noop
}
public
function
onIncrement
(
&
$count
)
{
$count
++;
}
public
function
onX_Y__Increment
(
&
$count
)
{
$count
++;
}
public
function
onAppend
(
&
$list
)
{
$list
[]
=
'FooExtension'
;
}
}
class
AbortHooks1
{
public
function
onAbort
(
&
$called
)
{
$called
[]
=
1
;
return
true
;
}
}
class
AbortHooks2
{
public
function
onAbort
(
&
$called
)
{
$called
[]
=
2
;
return
false
;
}
}
class
AbortHooks3
{
public
function
onAbort
(
&
$called
)
{
$called
[]
=
3
;
return
true
;
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 20:50 (5 d, 2 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
5c/5d/4f04373bdb7fac2e1e2657545569
Default Alt Text
HookContainerTest.php (35 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment