Can you run PHPUnit tests from a script? - php

I have a PHP deployment script that I want to run PHPUnit tests first, and stop if the tests fail. I've been googling this a lot, and it's very hard to find documentation on running unit tests from php, rather than from the command line tool.
For the newest version of PHPUnit, can you do something like:
$unit_tests = new PHPUnit('my_tests_dir');
$passed = $unit_tests->run();
Preferably a solution that doesn't require me to manually specify each test suite.

Figured it out:
$phpunit = new PHPUnit_TextUI_TestRunner;
try {
$test_results = $phpunit->dorun($phpunit->getTest(__DIR__, '', 'Test.php'));
} catch (PHPUnit_Framework_Exception $e) {
print $e->getMessage() . "\n";
die ("Unit tests failed.");
}

Simplest way to do this is by instantiating object of class PHPUnit_TextUI_Command.
So here is an example:
require '/usr/share/php/PHPUnit/Autoload.php';
function dummy($input)
{
return '';
}
//Prevent PHPUnit from outputing anything
ob_start('dummy');
//Run PHPUnit and log results to results.xml in junit format
$command = new PHPUnit_TextUI_Command;
$command->run(array('phpunit', '--log-junit', 'results.xml', 'PHPUnitTest.php'),
true);
ob_end_clean();
This way the results will be logged in results.xml file in junit format that can be parsed. If you need a different format you can check the documentation. Also you can add more options by changing the array passed to run method.

working with PHPUnit 7.5:
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
$test = new TestSuite();
$test->addTestSuite(MyTest::class);
$result = $test->run();
and $result object contains lot of usefull data:
$result->errors()
$result->failures
$result->wasSuccessful()
etc...

Solution for PHP7 & phpunit ^7
use PHPUnit\TextUI\Command;
$command = new Command();
$command->run(['phpunit', 'tests']);
Does the same effect as CLI command:
vendor/bin/phpunit --bootstrap vendor/autoload.php tests

It doesn't seem like PHPUnit has any built-in configuration to prevent it from dumping its output directly into the response (at least not as of PHPUnit 5.7).
So, I used ob_start to shunt output to a variable, and set the third argument of doRun to false to prevent PHPUnit from halting the script:
<?php
$suite = new PHPUnit_Framework_TestSuite();
$suite->addTestSuite('App\Tests\DatabaseTests');
// Shunt output of PHPUnit to a variable
ob_start();
$runner = new PHPUnit_TextUI_TestRunner;
$runner->doRun($suite, [], false);
$result = ob_get_clean();
// Print the output of PHPUnit wherever you want
print_r($result);

Related

Call PHP-CLI script without Shell from PHP

I want to run some vendor-scripts of Composer from my PHP-script.
Calling each command takes long although the command itself finishes quick. I assume creating a new shell by shell_exec() takes some time.
I wanted to call the PHP-scripts directly via the require keyword but changing the global $argv to contain the parameters for a script does not apply to the called script. Is $argv implicitly immutable across script-files or do I have another error in my way of thinking?
Here is some sample code (should be executed via CLI, not tested):
namespace Foo;
class Bar
{
public static function call_cs_fixer()
{
$GLOBALS['argv'] = [
'/path/to/vendor/bin/php-cs-fixer',
'fix',
'--config',
'"/path/to/.php-cs-fixer.php"',
'"/path/to/project"',
];
return require $GLOBALS['argv'][0];
}
}
echo \Foo\Bar::call_cs_fixer();
Is $argv implicitly immutable across script-files or do I have another error in my way of thinking?
That depends on how the script/utility works that you try to invoke. Which means you can't expect it to work stable and I would refrain from it unless you know it has this interface. As you don't know it - otherwise you would not ask the question that way - throw that idea into the bin in this case.
I assume creating a new shell by shell_exec() takes some time.
This may be (we can't look into your system configuration), but if it is a linux system this is very likely not the case.
In practice, the use of a new shell sub-process to invoke the tooling is the much, much better way to do things here. This is also how composer(1) invokes scripts (see Scripts) - unless they are bound as (static) methods - and is always true for the composer exec command.
The reason is that you can control not only the command line arguments much better but also the working directory and the environment parameters (a.k.a. environment variables or environment in short), compare proc_open(php). The standard streams are available as well.
As you're running in context of composer, and if you have access to the sources of it (e.g. you bind a composer script or hook in your composer.json configuration), you can use the process components that ship with composer itself (its all PHP), it has quite some utility in there.
If you just want to start lightly, I found the passthru(php) function a good fit for quickly getting started:
// the command you'd like to execute
$command = '/path/to/vendor/bin/php-cs-fixer';
$args = [
'fix',
'--config',
'/path/to/.php-cs-fixer.php',
'/path/to/project'
];
// build the command-line
$commandLine = sprintf(
'%s %s',
$command,
implode(' ', array_map('escapeshellarg', $args))
);
// execute
$result = passthru($commandLine, $exitStatus);
// be verbose and give some debug info
fprintf(
STDERR,
"debug: command %s exited with status %d\n",
$commandLine,
$exitStatus
);
// throw on exit status != 0, a convention only but you often want this
if (false === $result || $existStatus !== 0) {
throw new \RuntimeException(sprintf(
'command "%s" exited with non-zero status %d (result=%s).\n',
addcslashes($commandLine, "\0..\37\"\\\177..\377"),
$exitStatus,
var_export($result, true)
), (int)$exitStatus);
}

Calling console command security:check from controller action produces Lock file not found response

(Symfony3)
I'm toying with the idea of setting up some simple cron tasks to generate security reports for our project managers so that they can schedule upgrade time for developers (vs. me forgetting to run them manually).
As a very basic check, I'll simply run...
php bin/console security:check
...to see what composer has to say about vulnerabilities. Ultimately I'd like to roll this output into an email or post it to a slack channel or basecamp job when the cron is run.
Problem
When I run the command from via terminal it works great. Running the command inside a controller always returns the response Lock file does not exist. I'm assuming this in reference to the composer.lock file at the root of the project. I can confirm that this file does in fact exist.
Following is the controller I'm currently using, which is adapted from this:
http://symfony.com/doc/current/console/command_in_controller.html
<?php
namespace Treetop1500\SecurityReportBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class DefaultController extends Controller
{
public function indexAction($key)
{
if ($key != $this->getParameter('easy_cron_key')) {
throw new UnauthorizedHttpException("You are not authorized to access this page.");
}
$kernel = $this->get('kernel');
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => 'security:check'
));
// You can use NullOutput() if you don't need the output
$output = new BufferedOutput();
$application->run($input, $output);
// return the output, don't use if you used NullOutput()
$content = $output->fetch();
// return new Response(""), if you used NullOutput()
return new Response($content);
}
}
$content always has the value "Lock file does not exist."
I realize there are probably better tools and ways to do this, however I would really like to understand why this is the generated response from in this controller action. Thank you for taking a look!
Pass absolute path to composer.lock file just like that:
php bin/console security:check /path/to/another/composer.lock
So in your example, that's would be:
$input = new ArrayInput([
'command' => 'security:check',
'lockfile' => '/path/to/another/composer.lock'
]);
Read more: SecurityCheckerCommand from SensioLabs. Optional argument is lockfile, which is checked by SecurityChecker. On line 46, they are looking for composer.lock file (default argument) and throw an exception, when they not found.
P.S. Earlier, I type the wrong parameters to array. I checked in Symfony documentation (How to Call Other Commands) and fixed the answer.
The solution to this is to pass the lockfile argument to the ArrayInput object like this:
$lockfile = $this->get('kernel')->getRootDir()."/../composer.lock";
$input = new ArrayInput(array('command'=>'security:check','lockfile'=>$lockfile));

how to run phpunit from code?

I want to use a local installation of PHPUnit (via composer) to run my tests and display it on screen (acessing /admin/tests for instance). But the only way to run tests I found in the documentation was the command line tool.
Bellow is an hypothetical example of what I'm looking for:
$session = new PHPUnit_TestSession('path/to/folder');
$results = $session->runAll();
echo $results->failuresCount();
// other hipotetical $result->methods...
// maybe $results->dump()
This may be an overkill but you are in for a treat: https://github.com/NSinopoli/VisualPHPUnit :)
EDIT Here is a rudimentary use of PHPUnit using the TextUI_TestRunner
// make sure you have PHPUnit on your path
require_once "PHPUnit/Framework/TestSuite.php";
require_once "PHPUnit/TextUI/TestRunner.php";
$suite = new PHPUnit_Framework_TestSuite();
$suite->addTestSuite('YourTestCase');
// run the test suite with TextUI_TestRunner
PHPUnit_TextUI_TestRunner::run($suite);
The YourTestCase class is a subclass of PHPUnit_Framework_TestCase, which you can read more on how to write at the official website: http://www.phpunit.de/manual/3.2/en/writing-tests-for-phpunit.html
However, I'd also recommend getting a copy of this book: http://www.amazon.com/Advanced-PHP-Programming-George-Schlossnagle/dp/0672325616 The author teaches you quite a few cool tricks, including autoloading tests, etc.

integration testing command line PHP

I've got a PHP script that runs at the command line, executing classes that are already unit tested with PHPUnit.
However, I'd like to verify that the script itself has no logical errors and runs properly.
// require classes
require_once 'injectedClass.php';
require_once 'DBClass.php';
require_once 'taskEngine.php';
$injectedObj = new injectedClass;
$dbObj = new DBClass;
$taskRunner = new taskEngine($injectedObj, $dbObj);
$taskRunner->task1()->task2();
$taskRunner->finish();
//etc
Updated Solution
It is as simple as djechelon's answer suggested, I was overthinking it. The solution is to create a PHPUnit test and pre-assign the variables passed into the taskRunner to mock objects. In the live script, a simple check before creating real objects allows the same script to be used for testing and production:
test:
$injectedObj = $this->getMock('injectedClass');
$dbObj = $this->getMock('DBClass');
require_once '/path/to/live/script.php';
$this->assertTrue($taskRunner->finished);
script:
// require classes
if(!isset($injectedObj)) {
$injectedObj = new injectedClass;
}
if(!isset($dbObj)) {
$dbObj = new DBClass;
}
$taskRunner = new taskEngine($injectedObj, $dbObj);
// run tasks
Can't you create a PHPUnit test for your script?
You could perform an integration test by hand, creating a script that runs your script with a set of given input parameters and compare its output to what you could expect.
Beware of the chicken-and-egg problem: your testing script cannot be tested itself by a test bench...
Anyway I'm not sure you need testing your script if it's so simple. A few manual runs might suffice...

Is there a way to execute php code in a sandbox from within php

I want to execute a php-script from php that will use different constants and different versions of classes that are already defined.
Is there a sandbox php_module where i could just:
sandbox('script.php'); // run in a new php environment
instead of
include('script.php'); // run in the same environment
Or is proc_open() the only option?
PS: The script isn't accessible through the web, so fopen('http://host/script.php') is not an option.
There is runkit, but you may find it simpler to just call the script over the command line (Use shell_exec), if you don't need any interaction between the master and child processes.
The is a class on GitHub that may help, early stages but looks promising.
https://github.com/fregster/PHPSandbox
Also, you should look at the backtick operator:
$sOutput = `php script_to_run.php`;
This will allow you to inspect the output from the script you are running. However, note that the script will be run with the privileges you have, but you can circumvent this by using sudo on Linux.
This approach also assumes that you have the PHP CLI installed, which is not always the case.
There is Runkit_Sandbox - you might get it to work, it's a PHP extension. I'd say the way to go.
But you might need to create a "sandbox" your own, e.g. by resetting the global variable state of the superglobals you use.
class SandboxState
{
private $members = array('_GET', '_POST');
private $store = array();
public function save() {
foreach($members as $name) {
$this->store[$name] = $$name;
$$name = NULL;
}
}
public function restore() {
foreach($members as $name) {
$$name = $this->store[$name];
$this->store[$name] = NULL;
}
}
}
Usage:
$state = new SanddboxState();
$state->save();
// compile your get/post request by setting the superglobals
$_POST['submit'] = 'submit';
...
// execute your script:
$exec = function() {
include(func_get_arg(0)));
};
$exec('script.php');
// check the outcome.
...
// restore your own global state:
$state->restore();
dynamic plugin function execution that allows the loaded file and function to execute anything it wants, however it can only take and return variables that can be json_encode'ed.
function proxyExternalFunction($fileName, $functionName, $args, $setupStatements = '') {
$output = array();
$command = $setupStatements.";include('".addslashes($fileName)."');echo json_encode(".$functionName."(";
foreach ($args as $arg) {
$command .= "json_decode('".json_encode($arg)."',true),";
}
if (count($args) > 0) {
$command[strlen($command)-1] = ")";//end of $functionName
}
$command .= ");";//end of json_encode
$command = "php -r ".escapeshellarg($command);
exec($command, $output);
$output = json_decode($output,true);
}
the external code is totally sandboxed and you can apply any permission restrictions you want by doing sudo -u restricedUser php -r ....
I have developed a BSD-licensed sandbox class for this very purpose. It utilizes the PHPParser library to analyze the sandboxed code, check it against user-configurable whitelists and blacklists, and features a wide array of configuration options along with sane default settings. For your needs you can easily redefine classes called in your sandboxed code and route them to different ones.
The project also includes a sandbox toolkit (use only on your local machine!) that can be used to experiment with the sandbox settings, and a full manual and API documentation.
https://github.com/fieryprophet/php-sandbox
i know its not 100% topic related, but maybe useful for somebody n__n
function require_sandbox($__file,$__params=null,$__output=true) {
/* original from http://stackoverflow.com/a/3850454/209797 */
if($__params and is_array($__params))
extract($__params);
ob_start();
$__returned=require $__file;
$__contents=ob_get_contents();
ob_end_clean();
if($__output)
echo $__contents;
else
return $__returned;
};

Categories