Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2889097
PhutilTestCase.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
24 KB
Referenced Files
None
Subscribers
None
PhutilTestCase.php
View Options
<?php
/**
* Base test case for the very simple libphutil test framework.
*
* @task assert Making Test Assertions
* @task exceptions Exception Handling
* @task hook Hooks for Setup and Teardown
* @task internal Internals
*/
abstract
class
PhutilTestCase
extends
Phobject
{
private
$assertions
=
0
;
private
$runningTest
;
private
$testStartTime
;
private
$results
=
array
(
)
;
private
$enableCoverage
;
private
$coverage
=
array
(
)
;
private
$workingCopy
;
private
$paths
;
private
$renderer
;
private
static
$executables
=
array
(
)
;
/* -( Making Test Assertions )--------------------------------------------- */
/**
* Assert that a value is `false`, strictly. The test fails if it is not.
*
* @param wild The empirically derived value, generated by executing the
* test.
* @param string A human-readable description of what these values represent,
* and particularly of what a discrepancy means.
*
* @return void
* @task assert
*/
final
protected
function
assertFalse
(
$result
,
$message
=
null
)
{
if
(
$result
===
false
)
{
$this
->
assertions
++
;
return
;
}
$this
->
failAssertionWithExpectedValue
(
'false'
,
$result
,
$message
)
;
}
/**
* Assert that a value is `true`, strictly. The test fails if it is not.
*
* @param wild The empirically derived value, generated by executing the
* test.
* @param string A human-readable description of what these values represent,
* and particularly of what a discrepancy means.
*
* @return void
* @task assert
*/
final
protected
function
assertTrue
(
$result
,
$message
=
null
)
{
if
(
$result
===
true
)
{
$this
->
assertions
++
;
return
;
}
$this
->
failAssertionWithExpectedValue
(
'true'
,
$result
,
$message
)
;
}
/**
* Assert that two values are equal, strictly. The test fails if they are not.
*
* NOTE: This method uses PHP's strict equality test operator (`===`) to
* compare values. This means values and types must be equal, key order must
* be identical in arrays, and objects must be referentially identical.
*
* @param wild The theoretically expected value, generated by careful
* reasoning about the properties of the system.
* @param wild The empirically derived value, generated by executing the
* test.
* @param string A human-readable description of what these values represent,
* and particularly of what a discrepancy means.
*
* @return void
* @task assert
*/
final
protected
function
assertEqual
(
$expect
,
$result
,
$message
=
null
)
{
if
(
$expect
===
$result
)
{
$this
->
assertions
++
;
return
;
}
$expect
=
PhutilReadableSerializer
::
printableValue
(
$expect
)
;
$result
=
PhutilReadableSerializer
::
printableValue
(
$result
)
;
$caller
=
self
::
getCallerInfo
(
)
;
$file
=
$caller
[
'file'
]
;
$line
=
$caller
[
'line'
]
;
if
(
$message
!==
null
)
{
$output
=
pht
(
'Assertion failed, expected values to be equal (at %s:%d): %s'
,
$file
,
$line
,
$message
)
;
}
else
{
$output
=
pht
(
'Assertion failed, expected values to be equal (at %s:%d).'
,
$file
,
$line
)
;
}
$output
.=
"\n"
;
if
(
strpos
(
$expect
,
"\n"
)
===
false
&&
strpos
(
$result
,
"\n"
)
===
false
)
{
$output
.=
pht
(
"Expected: %s\n Actual: %s"
,
$expect
,
$result
)
;
}
else
{
$output
.=
pht
(
"Expected vs Actual Output Diff\n%s"
,
ArcanistDiffUtils
::
renderDifferences
(
$expect
,
$result
,
$lines
=
0xFFFF
)
)
;
}
$this
->
failTest
(
$output
)
;
throw
new
PhutilTestTerminatedException
(
$output
)
;
}
/**
* Assert an unconditional failure. This is just a convenience method that
* better indicates intent than using dummy values with assertEqual(). This
* causes test failure.
*
* @param string Human-readable description of the reason for test failure.
* @return void
* @task assert
*/
final
protected
function
assertFailure
(
$message
)
{
$this
->
failTest
(
$message
)
;
throw
new
PhutilTestTerminatedException
(
$message
)
;
}
/**
* End this test by asserting that the test should be skipped for some
* reason.
*
* @param string Reason for skipping this test.
* @return void
* @task assert
*/
final
protected
function
assertSkipped
(
$message
)
{
$this
->
skipTest
(
$message
)
;
throw
new
PhutilTestSkippedException
(
$message
)
;
}
final
protected
function
assertCaught
(
$expect
,
$actual
,
$message
=
null
)
{
if
(
$message
!==
null
)
{
$message
=
phutil_string_cast
(
$message
)
;
}
if
(
$actual
===
null
)
{
// This is okay: no exception.
}
else
if
(
$actual
instanceof
Exception
)
{
// This is also okay.
}
else
if
(
$actual
instanceof
Throwable
)
{
// And this is okay too.
}
else
{
// Anything else is no good.
if
(
$message
!==
null
)
{
$output
=
pht
(
'Call to "assertCaught(..., <junk>, ...)" for test case "%s" '
.
'passed bad value for test result. Expected null, Exception, '
.
'or Throwable; got: %s.'
,
$message
,
phutil_describe_type
(
$actual
)
)
;
}
else
{
$output
=
pht
(
'Call to "assertCaught(..., <junk>, ...)" passed bad value for '
.
'test result. Expected null, Exception, or Throwable; got: %s.'
,
phutil_describe_type
(
$actual
)
)
;
}
$this
->
failTest
(
$output
)
;
throw
new
PhutilTestTerminatedException
(
$output
)
;
}
$expect_list
=
null
;
if
(
$expect
===
false
)
{
$expect_list
=
array
(
)
;
}
else
if
(
$expect
===
true
)
{
$expect_list
=
array
(
'Exception'
,
'Throwable'
,
)
;
}
else
if
(
is_string
(
$expect
)
||
is_array
(
$expect
)
)
{
$list
=
(array)
$expect
;
$items_ok
=
true
;
foreach
(
$list
as
$key
=>
$item
)
{
if
(
!
phutil_nonempty_stringlike
(
$item
)
)
{
$items_ok
=
false
;
break
;
}
$list
[
$key
]
=
phutil_string_cast
(
$item
)
;
}
if
(
$items_ok
)
{
$expect_list
=
$list
;
}
}
if
(
$expect_list
===
null
)
{
if
(
$message
!==
null
)
{
$output
=
pht
(
'Call to "assertCaught(<junk>, ...)" for test case "%s" '
.
'passed bad expected value. Expected bool, class name as a string, '
.
'or a list of class names. Got: %s.'
,
$message
,
phutil_describe_type
(
$expect
)
)
;
}
else
{
$output
=
pht
(
'Call to "assertCaught(<junk>, ...)" passed bad expected value. '
.
'expected result. Expected null, Exception, or Throwable; got: %s.'
,
phutil_describe_type
(
$expect
)
)
;
}
$this
->
failTest
(
$output
)
;
throw
new
PhutilTestTerminatedException
(
$output
)
;
}
if
(
$actual
===
null
)
{
$is_match
=
!
$expect_list
;
}
else
{
$is_match
=
false
;
foreach
(
$expect_list
as
$exception_class
)
{
if
(
$actual
instanceof
$exception_class
)
{
$is_match
=
true
;
break
;
}
}
}
if
(
$is_match
)
{
$this
->
assertions
++
;
return
;
}
$caller
=
self
::
getCallerInfo
(
)
;
$file
=
$caller
[
'file'
]
;
$line
=
$caller
[
'line'
]
;
$output
=
array
(
)
;
if
(
$message
!==
null
)
{
$output
[
]
=
pht
(
'Assertion of caught exception failed (at %s:%d in test case "%s").'
,
$file
,
$line
,
$message
)
;
}
else
{
$output
[
]
=
pht
(
'Assertion of caught exception failed (at %s:%d).'
,
$file
,
$line
)
;
}
if
(
$actual
===
null
)
{
$output
[
]
=
pht
(
'Expected any exception, got no exception.'
)
;
}
else
if
(
!
$expect_list
)
{
$output
[
]
=
pht
(
'Expected no exception, got exception of class "%s".'
,
get_class
(
$actual
)
)
;
}
else
{
$expected_classes
=
implode
(
', '
,
$expect_list
)
;
$output
[
]
=
pht
(
'Expected exception (in class(es): %s), got exception of class "%s".'
,
$expected_classes
,
get_class
(
$actual
)
)
;
}
$output
=
implode
(
"\n\n"
,
$output
)
;
$this
->
failTest
(
$output
)
;
throw
new
PhutilTestTerminatedException
(
$output
)
;
}
/* -( Exception Handling )------------------------------------------------- */
/**
* This simplest way to assert exceptions are thrown.
*
* @param exception The expected exception.
* @param callable The thing which throws the exception.
*
* @return void
* @task exceptions
*/
final
protected
function
assertException
(
$expected_exception_class
,
$callable
)
{
$this
->
tryTestCases
(
array
(
'assertException'
=>
array
(
)
)
,
array
(
false
)
,
$callable
,
$expected_exception_class
)
;
}
/**
* Straightforward method for writing unit tests which check if some block of
* code throws an exception. For example, this allows you to test the
* exception behavior of ##is_a_fruit()## on various inputs:
*
* public function testFruit() {
* $this->tryTestCases(
* array(
* 'apple is a fruit' => new Apple(),
* 'rock is not a fruit' => new Rock(),
* ),
* array(
* true,
* false,
* ),
* array($this, 'tryIsAFruit'),
* 'NotAFruitException');
* }
*
* protected function tryIsAFruit($input) {
* is_a_fruit($input);
* }
*
* @param map Map of test case labels to test case inputs.
* @param list List of expected results, true to indicate that the case
* is expected to succeed and false to indicate that the case
* is expected to throw.
* @param callable Callback to invoke for each test case.
* @param string Optional exception class to catch, defaults to
* 'Exception'.
* @return void
* @task exceptions
*/
final
protected
function
tryTestCases
(
array
$inputs
,
array
$expect
,
$callable
,
$exception_class
=
'Exception'
)
{
if
(
count
(
$inputs
)
!==
count
(
$expect
)
)
{
$this
->
assertFailure
(
pht
(
'Input and expectations must have the same number of values.'
)
)
;
}
$labels
=
array_keys
(
$inputs
)
;
$inputs
=
array_values
(
$inputs
)
;
$expecting
=
array_values
(
$expect
)
;
foreach
(
$inputs
as
$idx
=>
$input
)
{
$expect
=
$expecting
[
$idx
]
;
$label
=
$labels
[
$idx
]
;
$caught
=
null
;
try
{
call_user_func
(
$callable
,
$input
)
;
}
catch
(
Exception
$ex
)
{
if
(
$ex
instanceof
PhutilTestTerminatedException
)
{
throw
$ex
;
}
if
(
!
(
$ex
instanceof
$exception_class
)
)
{
throw
$ex
;
}
$caught
=
$ex
;
}
$actual
=
!
(
$caught
instanceof
Exception
)
;
if
(
$expect
===
$actual
)
{
if
(
$expect
)
{
$message
=
pht
(
"Test case '%s' did not throw, as expected."
,
$label
)
;
}
else
{
$message
=
pht
(
"Test case '%s' threw, as expected."
,
$label
)
;
}
}
else
{
if
(
$expect
)
{
$message
=
pht
(
"Test case '%s' was expected to succeed, but it "
.
"raised an exception of class %s with message: %s"
,
$label
,
get_class
(
$ex
)
,
$ex
->
getMessage
(
)
)
;
}
else
{
$message
=
pht
(
"Test case '%s' was expected to raise an "
.
"exception, but it did not throw anything."
,
$label
)
;
}
}
$this
->
assertEqual
(
$expect
,
$actual
,
$message
)
;
}
}
/**
* Convenience wrapper around @{method:tryTestCases} for cases where your
* inputs are scalar. For example:
*
* public function testFruit() {
* $this->tryTestCaseMap(
* array(
* 'apple' => true,
* 'rock' => false,
* ),
* array($this, 'tryIsAFruit'),
* 'NotAFruitException');
* }
*
* protected function tryIsAFruit($input) {
* is_a_fruit($input);
* }
*
* For cases where your inputs are not scalar, use @{method:tryTestCases}.
*
* @param map Map of scalar test inputs to expected success (true
* expects success, false expects an exception).
* @param callable Callback to invoke for each test case.
* @param string Optional exception class to catch, defaults to
* 'Exception'.
* @return void
* @task exceptions
*/
final
protected
function
tryTestCaseMap
(
array
$map
,
$callable
,
$exception_class
=
'Exception'
)
{
return
$this
->
tryTestCases
(
array_fuse
(
array_keys
(
$map
)
)
,
array_values
(
$map
)
,
$callable
,
$exception_class
)
;
}
/* -( Hooks for Setup and Teardown )--------------------------------------- */
/**
* This hook is invoked once, before any tests in this class are run. It
* gives you an opportunity to perform setup steps for the entire class.
*
* @return void
* @task hook
*/
protected
function
willRunTests
(
)
{
return
;
}
/**
* This hook is invoked once, after any tests in this class are run. It gives
* you an opportunity to perform teardown steps for the entire class.
*
* @return void
* @task hook
*/
protected
function
didRunTests
(
)
{
return
;
}
/**
* This hook is invoked once per test, before the test method is invoked.
*
* @param string Method name of the test which will be invoked.
* @return void
* @task hook
*/
protected
function
willRunOneTest
(
$test_method_name
)
{
return
;
}
/**
* This hook is invoked once per test, after the test method is invoked.
*
* @param string Method name of the test which was invoked.
* @return void
* @task hook
*/
protected
function
didRunOneTest
(
$test_method_name
)
{
return
;
}
/**
* This hook is invoked once, before any test cases execute. It gives you
* an opportunity to perform setup steps for the entire suite of test cases.
*
* @param list<PhutilTestCase> List of test cases to be run.
* @return void
* @task hook
*/
public
function
willRunTestCases
(
array
$test_cases
)
{
return
;
}
/**
* This hook is invoked once, after all test cases execute.
*
* @param list<PhutilTestCase> List of test cases that ran.
* @return void
* @task hook
*/
public
function
didRunTestCases
(
array
$test_cases
)
{
return
;
}
/* -( Internals )---------------------------------------------------------- */
/**
* Construct a new test case. This method is ##final##, use willRunTests() to
* provide test-wide setup logic.
*
* @task internal
*/
final
public
function
__construct
(
)
{
}
/**
* Mark the currently-running test as a failure.
*
* @param string Human-readable description of problems.
* @return void
*
* @task internal
*/
private
function
failTest
(
$reason
)
{
$this
->
resultTest
(
ArcanistUnitTestResult
::
RESULT_FAIL
,
$reason
)
;
}
/**
* This was a triumph. I'm making a note here: HUGE SUCCESS.
*
* @param string Human-readable overstatement of satisfaction.
* @return void
*
* @task internal
*/
private
function
passTest
(
$reason
)
{
$this
->
resultTest
(
ArcanistUnitTestResult
::
RESULT_PASS
,
$reason
)
;
}
/**
* Mark the current running test as skipped.
*
* @param string Description for why this test was skipped.
* @return void
* @task internal
*/
private
function
skipTest
(
$reason
)
{
$this
->
resultTest
(
ArcanistUnitTestResult
::
RESULT_SKIP
,
$reason
)
;
}
private
function
resultTest
(
$test_result
,
$reason
)
{
$coverage
=
$this
->
endCoverage
(
)
;
$result
=
new
ArcanistUnitTestResult
(
)
;
$result
->
setCoverage
(
$coverage
)
;
$result
->
setNamespace
(
get_class
(
$this
)
)
;
$result
->
setName
(
$this
->
runningTest
)
;
$result
->
setLink
(
$this
->
getLink
(
$this
->
runningTest
)
)
;
$result
->
setResult
(
$test_result
)
;
$result
->
setDuration
(
microtime
(
true
)
-
$this
->
testStartTime
)
;
$result
->
setUserData
(
$reason
)
;
$this
->
results
[
]
=
$result
;
if
(
$this
->
renderer
)
{
echo
$this
->
renderer
->
renderUnitResult
(
$result
)
;
}
}
/**
* Execute the tests in this test case. You should not call this directly;
* use @{class:PhutilUnitTestEngine} to orchestrate test execution.
*
* @return void
* @task internal
*/
final
public
function
run
(
)
{
$this
->
results
=
array
(
)
;
$reflection
=
new
ReflectionClass
(
$this
)
;
$methods
=
$reflection
->
getMethods
(
)
;
// Try to ensure that poorly-written tests which depend on execution order
// (and are thus not properly isolated) will fail.
shuffle
(
$methods
)
;
$this
->
willRunTests
(
)
;
foreach
(
$methods
as
$method
)
{
$name
=
$method
->
getName
(
)
;
if
(
preg_match
(
'/^test/'
,
$name
)
)
{
$this
->
runningTest
=
$name
;
$this
->
assertions
=
0
;
$this
->
testStartTime
=
microtime
(
true
)
;
try
{
$this
->
willRunOneTest
(
$name
)
;
$this
->
beginCoverage
(
)
;
$exceptions
=
array
(
)
;
try
{
call_user_func_array
(
array
(
$this
,
$name
)
,
array
(
)
)
;
$this
->
passTest
(
pht
(
'%s assertion(s) passed.'
,
new
PhutilNumber
(
$this
->
assertions
)
)
)
;
}
catch
(
Exception
$ex
)
{
$exceptions
[
'Execution'
]
=
$ex
;
}
try
{
$this
->
didRunOneTest
(
$name
)
;
}
catch
(
Exception
$ex
)
{
$exceptions
[
'Shutdown'
]
=
$ex
;
}
if
(
$exceptions
)
{
if
(
count
(
$exceptions
)
==
1
)
{
throw
head
(
$exceptions
)
;
}
else
{
throw
new
PhutilAggregateException
(
pht
(
'Multiple exceptions were raised during test execution.'
)
,
$exceptions
)
;
}
}
if
(
!
$this
->
assertions
)
{
$this
->
failTest
(
pht
(
'This test case made no assertions. Test cases must make at '
.
'least one assertion.'
)
)
;
}
}
catch
(
PhutilTestTerminatedException
$ex
)
{
// Continue with the next test.
}
catch
(
PhutilTestSkippedException
$ex
)
{
// Continue with the next test.
}
catch
(
Exception
$ex
)
{
$ex_class
=
get_class
(
$ex
)
;
$ex_message
=
$ex
->
getMessage
(
)
;
$ex_trace
=
$ex
->
getTraceAsString
(
)
;
$message
=
sprintf
(
"%s (%s): %s\n%s"
,
pht
(
'EXCEPTION'
)
,
$ex_class
,
$ex_message
,
$ex_trace
)
;
$this
->
failTest
(
$message
)
;
}
}
}
$this
->
didRunTests
(
)
;
return
$this
->
results
;
}
final
public
function
setEnableCoverage
(
$enable_coverage
)
{
$this
->
enableCoverage
=
$enable_coverage
;
return
$this
;
}
/**
* @phutil-external-symbol function xdebug_start_code_coverage
*/
private
function
beginCoverage
(
)
{
if
(
!
$this
->
enableCoverage
)
{
return
;
}
$this
->
assertCoverageAvailable
(
)
;
xdebug_start_code_coverage
(
XDEBUG_CC_UNUSED
|
XDEBUG_CC_DEAD_CODE
)
;
}
/**
* @phutil-external-symbol function xdebug_get_code_coverage
* @phutil-external-symbol function xdebug_stop_code_coverage
*/
private
function
endCoverage
(
)
{
if
(
!
$this
->
enableCoverage
)
{
return
;
}
$result
=
xdebug_get_code_coverage
(
)
;
xdebug_stop_code_coverage
(
$cleanup
=
false
)
;
$coverage
=
array
(
)
;
foreach
(
$result
as
$file
=>
$report
)
{
$project_root
=
$this
->
getProjectRoot
(
)
;
if
(
strncmp
(
$file
,
$project_root
,
strlen
(
$project_root
)
)
)
{
continue
;
}
$max
=
max
(
array_keys
(
$report
)
)
;
$str
=
''
;
for
(
$ii
=
1
;
$ii
<=
$max
;
$ii
++
)
{
$c
=
null
;
if
(
isset
(
$report
[
$ii
]
)
)
{
$c
=
$report
[
$ii
]
;
}
if
(
$c
===
-
1
)
{
$str
.=
'U'
;
// Un-covered.
}
else
if
(
$c
===
-
2
)
{
// TODO: This indicates "unreachable", but it flags the closing braces
// of functions which end in "return", which is super ridiculous. Just
// ignore it for now.
//
// See http://bugs.xdebug.org/view.php?id=1041
$str
.=
'N'
;
// Not executable.
}
else
if
(
$c
===
1
)
{
$str
.=
'C'
;
// Covered.
}
else
{
$str
.=
'N'
;
// Not executable.
}
}
$coverage
[
substr
(
$file
,
strlen
(
$project_root
)
+
1
)
]
=
$str
;
}
// Only keep coverage information for files modified by the change. In
// the case of --everything, we won't have paths, so just return all the
// coverage data.
if
(
$this
->
paths
)
{
$coverage
=
array_select_keys
(
$coverage
,
$this
->
paths
)
;
}
return
$coverage
;
}
private
function
assertCoverageAvailable
(
)
{
if
(
!
function_exists
(
'xdebug_start_code_coverage'
)
)
{
throw
new
Exception
(
pht
(
"You've enabled code coverage but XDebug is not installed."
)
)
;
}
}
final
public
function
getWorkingCopy
(
)
{
return
$this
->
workingCopy
;
}
final
public
function
setWorkingCopy
(
ArcanistWorkingCopyIdentity
$working_copy
)
{
$this
->
workingCopy
=
$working_copy
;
return
$this
;
}
final
public
function
getProjectRoot
(
)
{
$working_copy
=
$this
->
getWorkingCopy
(
)
;
if
(
!
$working_copy
)
{
throw
new
PhutilInvalidStateException
(
'setWorkingCopy'
)
;
}
return
$working_copy
->
getProjectRoot
(
)
;
}
final
public
function
setPaths
(
array
$paths
)
{
$this
->
paths
=
$paths
;
return
$this
;
}
final
protected
function
getLink
(
$method
)
{
$base_uri
=
$this
->
getWorkingCopy
(
)
->
getProjectConfig
(
'phabricator.uri'
)
;
$uri
=
id
(
new
PhutilURI
(
$base_uri
)
)
->
setPath
(
"/diffusion/symbol/{$method}/"
)
->
setQueryParam
(
'context'
,
get_class
(
$this
)
)
->
setQueryParam
(
'jump'
,
'true'
)
->
setQueryParam
(
'lang'
,
'php'
)
;
return
(string)
$uri
;
}
final
public
function
setRenderer
(
ArcanistUnitRenderer
$renderer
)
{
$this
->
renderer
=
$renderer
;
return
$this
;
}
/**
* Returns info about the caller function.
*
* @return map
*/
private
static
function
getCallerInfo
(
)
{
$callee
=
array
(
)
;
$caller
=
array
(
)
;
$seen
=
false
;
foreach
(
array_slice
(
debug_backtrace
(
)
,
1
)
as
$location
)
{
$function
=
idx
(
$location
,
'function'
)
;
if
(
!
$seen
&&
preg_match
(
'/^assert[A-Z]/'
,
$function
)
)
{
$seen
=
true
;
$caller
=
$location
;
}
else
if
(
$seen
&&
!
preg_match
(
'/^assert[A-Z]/'
,
$function
)
)
{
$callee
=
$location
;
break
;
}
}
return
array
(
'file'
=>
basename
(
idx
(
$caller
,
'file'
)
)
,
'line'
=>
idx
(
$caller
,
'line'
)
,
'function'
=>
idx
(
$callee
,
'function'
)
,
'class'
=>
idx
(
$callee
,
'class'
)
,
'object'
=>
idx
(
$caller
,
'object'
)
,
'type'
=>
idx
(
$callee
,
'type'
)
,
'args'
=>
idx
(
$caller
,
'args'
)
,
)
;
}
/**
* Fail an assertion which checks that some result is equal to a specific
* value, like 'true' or 'false'. This prints a readable error message and
* fails the current test.
*
* This method throws and does not return.
*
* @param string Human readable description of the expected value.
* @param string The actual value.
* @param string|null Optional assertion message.
* @return void
* @task internal
*/
private
function
failAssertionWithExpectedValue
(
$expect_description
,
$actual_result
,
$message
)
{
$caller
=
self
::
getCallerInfo
(
)
;
$file
=
$caller
[
'file'
]
;
$line
=
$caller
[
'line'
]
;
if
(
$message
!==
null
)
{
$description
=
pht
(
"Assertion failed, expected '%s' (at %s:%d): %s"
,
$expect_description
,
$file
,
$line
,
$message
)
;
}
else
{
$description
=
pht
(
"Assertion failed, expected '%s' (at %s:%d)."
,
$expect_description
,
$file
,
$line
)
;
}
$actual_result
=
PhutilReadableSerializer
::
printableValue
(
$actual_result
)
;
$header
=
pht
(
'ACTUAL VALUE'
)
;
$output
=
$description
.
"\n\n"
.
$header
.
"\n"
.
$actual_result
;
$this
->
failTest
(
$output
)
;
throw
new
PhutilTestTerminatedException
(
$output
)
;
}
final
protected
function
assertExecutable
(
$binary
)
{
if
(
!
isset
(
self
::
$executables
[
$binary
]
)
)
{
switch
(
$binary
)
{
case
'xhpast'
:
$ok
=
true
;
if
(
!
PhutilXHPASTBinary
::
isAvailable
(
)
)
{
try
{
PhutilXHPASTBinary
::
build
(
)
;
}
catch
(
Exception
$ex
)
{
$ok
=
false
;
}
}
break
;
default
:
$ok
=
Filesystem
::
binaryExists
(
$binary
)
;
break
;
}
self
::
$executables
[
$binary
]
=
$ok
;
}
if
(
!
self
::
$executables
[
$binary
]
)
{
$this
->
assertSkipped
(
pht
(
'Required executable "%s" is not available.'
,
$binary
)
)
;
}
}
final
protected
function
getSupportExecutable
(
$executable
)
{
$root
=
dirname
(
phutil_get_library_root
(
'arcanist'
)
)
;
return
$root
.
'/support/unit/'
.
$executable
.
'.php'
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Jan 19 2025, 11:23 (5 w, 14 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1119448
Default Alt Text
PhutilTestCase.php (24 KB)
Attached To
Mode
rARC Arcanist
Attached
Detach File
Event Timeline
Log In to Comment