Changeset View
Changeset View
Standalone View
Standalone View
src/unit/engine/XUnitTestEngine.php
| <?php | <?php | ||||
| /** | /** | ||||
| * Uses xUnit (http://xunit.codeplex.com/) to test C# code. | * Uses xUnit (http://xunit.codeplex.com/) to test C# code. | ||||
| * | * | ||||
| * Assumes that when modifying a file with a path like `SomeAssembly/MyFile.cs`, | * Assumes that when modifying a file with a path like `SomeAssembly/MyFile.cs`, | ||||
| * that the test assembly that verifies the functionality of `SomeAssembly` is | * that the test assembly that verifies the functionality of `SomeAssembly` is | ||||
| * located at `SomeAssembly.Tests`. | * located at `SomeAssembly.Tests`. | ||||
| * | * | ||||
| * @group unitrun | |||||
| * @concrete-extensible | * @concrete-extensible | ||||
| */ | */ | ||||
| class XUnitTestEngine extends ArcanistBaseUnitTestEngine { | class XUnitTestEngine extends ArcanistBaseUnitTestEngine { | ||||
| protected $runtimeEngine; | protected $runtimeEngine; | ||||
| protected $buildEngine; | protected $buildEngine; | ||||
| protected $testEngine; | protected $testEngine; | ||||
| protected $projectRoot; | protected $projectRoot; | ||||
| protected $xunitHintPath; | protected $xunitHintPath; | ||||
| protected $discoveryRules; | protected $discoveryRules; | ||||
| /** | /** | ||||
| * This test engine supports running all tests. | * This test engine supports running all tests. | ||||
| */ | */ | ||||
| protected function supportsRunAllTests() { | protected function supportsRunAllTests() { | ||||
| return true; | return true; | ||||
| } | } | ||||
| /** | /** | ||||
| * Determines what executables and test paths to use. Between platforms | * Determines what executables and test paths to use. Between platforms this | ||||
| * this also changes whether the test engine is run under .NET or Mono. It | * also changes whether the test engine is run under .NET or Mono. It also | ||||
| * also ensures that all of the required binaries are available for the tests | * ensures that all of the required binaries are available for the tests to | ||||
| * to run successfully. | * run successfully. | ||||
| * | * | ||||
| * @return void | * @return void | ||||
| */ | */ | ||||
| protected function loadEnvironment() { | protected function loadEnvironment() { | ||||
| $this->projectRoot = $this->getWorkingCopy()->getProjectRoot(); | $this->projectRoot = $this->getWorkingCopy()->getProjectRoot(); | ||||
| // Determine build engine. | // Determine build engine. | ||||
| if (Filesystem::binaryExists('msbuild')) { | if (Filesystem::binaryExists('msbuild')) { | ||||
| Show All 31 Lines | protected function loadEnvironment() { | ||||
| } | } | ||||
| $xunit = $this->projectRoot.DIRECTORY_SEPARATOR.$this->xunitHintPath; | $xunit = $this->projectRoot.DIRECTORY_SEPARATOR.$this->xunitHintPath; | ||||
| if (file_exists($xunit) && $this->xunitHintPath !== null) { | if (file_exists($xunit) && $this->xunitHintPath !== null) { | ||||
| $this->testEngine = Filesystem::resolvePath($xunit); | $this->testEngine = Filesystem::resolvePath($xunit); | ||||
| } else if (Filesystem::binaryExists('xunit.console.clr4.exe')) { | } else if (Filesystem::binaryExists('xunit.console.clr4.exe')) { | ||||
| $this->testEngine = 'xunit.console.clr4.exe'; | $this->testEngine = 'xunit.console.clr4.exe'; | ||||
| } else { | } else { | ||||
| throw new Exception( | throw new Exception( | ||||
| "Unable to locate xUnit console runner. Configure ". | "Unable to locate xUnit console runner. Configure ". | ||||
| "it with the `unit.csharp.xunit.binary' option in .arcconfig"); | "it with the `unit.csharp.xunit.binary' option in .arcconfig"); | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * Main entry point for the test engine. Determines what assemblies to | * Main entry point for the test engine. Determines what assemblies to build | ||||
| * build and test based on the files that have changed. | * and test based on the files that have changed. | ||||
| * | * | ||||
| * @return array Array of test results. | * @return array Array of test results. | ||||
| */ | */ | ||||
| public function run() { | public function run() { | ||||
| $this->loadEnvironment(); | $this->loadEnvironment(); | ||||
| if ($this->getRunAllTests()) { | if ($this->getRunAllTests()) { | ||||
| Show All 35 Lines | foreach ($this->discoveryRules as $regex => $targets) { | ||||
| $exists = true; | $exists = true; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (!$exists) { | if (!$exists) { | ||||
| $results[] = array( | $results[] = array( | ||||
| 'project' => $project, | 'project' => $project, | ||||
| 'assembly' => $assembly); | 'assembly' => $assembly, | ||||
| ); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| Show All 38 Lines | foreach ($results as $result) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| /** | /** | ||||
| * If the `Build` directory exists, we assume that this is a multi-platform | * If the `Build` directory exists, we assume that this is a multi-platform | ||||
| * project that requires generation of C# project files. Because we want to | * project that requires generation of C# project files. Because we want to | ||||
| * test that the generation and subsequent build is whole, we need to | * test that the generation and subsequent build is whole, we need to | ||||
| * regenerate any projects in case the developer has added files through an | * regenerate any projects in case the developer has added files through an | ||||
| * IDE and then forgotten to add them to the respective `.definitions` file. | * IDE and then forgotten to add them to the respective `.definitions` file. | ||||
| * By regenerating the projects we ensure that any missing definition entries | * By regenerating the projects we ensure that any missing definition entries | ||||
| * will cause the build to fail. | * will cause the build to fail. | ||||
| * | * | ||||
| * @return array Array of test results. | * @return array Array of test results. | ||||
| */ | */ | ||||
| private function generateProjects() { | private function generateProjects() { | ||||
| // No "Build" directory; so skip generation of projects. | // No "Build" directory; so skip generation of projects. | ||||
| if (!is_dir(Filesystem::resolvePath($this->projectRoot.'/Build'))) { | if (!is_dir(Filesystem::resolvePath($this->projectRoot.'/Build'))) { | ||||
| return array(); | return array(); | ||||
| } | } | ||||
| // No "Protobuild.exe" file; so skip generation of projects. | // No "Protobuild.exe" file; so skip generation of projects. | ||||
| if (!is_file(Filesystem::resolvePath( | if (!is_file(Filesystem::resolvePath( | ||||
| $this->projectRoot.'/Protobuild.exe'))) { | $this->projectRoot.'/Protobuild.exe'))) { | ||||
| Show All 36 Lines | private function generateProjects() { | ||||
| $result->setDuration(microtime(true) - $regenerate_start); | $result->setDuration(microtime(true) - $regenerate_start); | ||||
| $results[] = $result; | $results[] = $result; | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| /** | /** | ||||
| * Build the projects relevant for the specified test assemblies and return | * Build the projects relevant for the specified test assemblies and return | ||||
| * the results of the builds as test results. This build also passes the | * the results of the builds as test results. This build also passes the | ||||
| * "SkipTestsOnBuild" parameter when building the projects, so that MSBuild | * "SkipTestsOnBuild" parameter when building the projects, so that MSBuild | ||||
| * conditionals can be used to prevent any tests running as part of the | * conditionals can be used to prevent any tests running as part of the | ||||
| * build itself (since the unit tester is about to run each of the tests | * build itself (since the unit tester is about to run each of the tests | ||||
| * individually). | * individually). | ||||
| * | * | ||||
| * @param array Array of test assemblies. | * @param array Array of test assemblies. | ||||
| * @return array Array of test results. | * @return array Array of test results. | ||||
| */ | */ | ||||
| Show All 30 Lines | foreach ($iterator as $test_assembly => $future) { | ||||
| $result->setDuration(microtime(true) - $build_start); | $result->setDuration(microtime(true) - $build_start); | ||||
| $results[] = $result; | $results[] = $result; | ||||
| } | } | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| /** | /** | ||||
| * Build the future for running a unit test. This can be | * Build the future for running a unit test. This can be overridden to enable | ||||
| * overridden to enable support for code coverage via | * support for code coverage via another tool. | ||||
| * another tool | |||||
| * | * | ||||
| * @param string Name of the test assembly. | * @param string Name of the test assembly. | ||||
| * @return array The future, output filename and coverage filename | * @return array The future, output filename and coverage filename | ||||
| * stored in an array. | * stored in an array. | ||||
| */ | */ | ||||
| protected function buildTestFuture($test_assembly) { | protected function buildTestFuture($test_assembly) { | ||||
| // FIXME: Can't use TempFile here as xUnit doesn't like | // FIXME: Can't use TempFile here as xUnit doesn't like | ||||
| // UNIX-style full paths. It sees the leading / as the | // UNIX-style full paths. It sees the leading / as the | ||||
| // start of an option flag, even when quoted. | // start of an option flag, even when quoted. | ||||
| $xunit_temp = Filesystem::readRandomCharacters(10).'.results.xml'; | $xunit_temp = Filesystem::readRandomCharacters(10).'.results.xml'; | ||||
| if (file_exists($xunit_temp)) { | if (file_exists($xunit_temp)) { | ||||
| unlink($xunit_temp); | unlink($xunit_temp); | ||||
| } | } | ||||
| $future = new ExecFuture( | $future = new ExecFuture( | ||||
| '%C %s /xml %s', | '%C %s /xml %s', | ||||
| trim($this->runtimeEngine.' '.$this->testEngine), | trim($this->runtimeEngine.' '.$this->testEngine), | ||||
| Show All 11 Lines | class XUnitTestEngine extends ArcanistBaseUnitTestEngine { | ||||
| /** | /** | ||||
| * Run the xUnit test runner on each of the assemblies and parse the | * Run the xUnit test runner on each of the assemblies and parse the | ||||
| * resulting XML. | * resulting XML. | ||||
| * | * | ||||
| * @param array Array of test assemblies. | * @param array Array of test assemblies. | ||||
| * @return array Array of test results. | * @return array Array of test results. | ||||
| */ | */ | ||||
| private function testAssemblies(array $test_assemblies) { | private function testAssemblies(array $test_assemblies) { | ||||
| $results = array(); | $results = array(); | ||||
| // Build the futures for running the tests. | // Build the futures for running the tests. | ||||
| $futures = array(); | $futures = array(); | ||||
| $outputs = array(); | $outputs = array(); | ||||
| $coverages = array(); | $coverages = array(); | ||||
| foreach ($test_assemblies as $test_assembly) { | foreach ($test_assemblies as $test_assembly) { | ||||
| list($future_r, $xunit_temp, $coverage) = | list($future_r, $xunit_temp, $coverage) = | ||||
| Show All 11 Lines | foreach (Futures($futures)->limit(8) as $test_assembly => $future) { | ||||
| $result = $this->parseTestResult( | $result = $this->parseTestResult( | ||||
| $outputs[$test_assembly], | $outputs[$test_assembly], | ||||
| $coverages[$test_assembly]); | $coverages[$test_assembly]); | ||||
| $results[] = $result; | $results[] = $result; | ||||
| unlink($outputs[$test_assembly]); | unlink($outputs[$test_assembly]); | ||||
| } else { | } else { | ||||
| // FIXME: There's a bug in Mono which causes a segmentation fault | // FIXME: There's a bug in Mono which causes a segmentation fault | ||||
| // when xUnit.NET runs; this causes the XML file to not appear | // when xUnit.NET runs; this causes the XML file to not appear | ||||
| // (depending on when the segmentation fault occurs). See | // (depending on when the segmentation fault occurs). See | ||||
| // https://bugzilla.xamarin.com/show_bug.cgi?id=16379 | // https://bugzilla.xamarin.com/show_bug.cgi?id=16379 | ||||
| // for more information. | // for more information. | ||||
| // Since it's not possible for the user to correct this error, we | // Since it's not possible for the user to correct this error, we | ||||
| // ignore the fact the tests didn't run here. | // ignore the fact the tests didn't run here. | ||||
| // | |||||
| } | } | ||||
| } | } | ||||
| return array_mergev($results); | return array_mergev($results); | ||||
| } | } | ||||
| /** | /** | ||||
| * Returns null for this implementation as xUnit does not support code | * Returns null for this implementation as xUnit does not support code | ||||
| * coverage directly. Override this method in another class to provide | * coverage directly. Override this method in another class to provide code | ||||
| * code coverage information (also see `CSharpToolsUnitEngine`). | * coverage information (also see @{class:CSharpToolsUnitEngine}). | ||||
| * | * | ||||
| * @param string The name of the coverage file if one was provided by | * @param string The name of the coverage file if one was provided by | ||||
| * `buildTestFuture`. | * `buildTestFuture`. | ||||
| * @return array Code coverage results, or null. | * @return array Code coverage results, or null. | ||||
| */ | */ | ||||
| protected function parseCoverageResult($coverage) { | protected function parseCoverageResult($coverage) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| /** | /** | ||||
| * Parses the test results from xUnit. | * Parses the test results from xUnit. | ||||
| * | * | ||||
| * @param string The name of the xUnit results file. | * @param string The name of the xUnit results file. | ||||
| * @param string The name of the coverage file if one was provided by | * @param string The name of the coverage file if one was provided by | ||||
| * `buildTestFuture`. This is passed through to | * `buildTestFuture`. This is passed through to | ||||
| * `parseCoverageResult`. | * `parseCoverageResult`. | ||||
| * @return array Test results. | * @return array Test results. | ||||
| */ | */ | ||||
| private function parseTestResult($xunit_tmp, $coverage) { | private function parseTestResult($xunit_tmp, $coverage) { | ||||
| $xunit_dom = new DOMDocument(); | $xunit_dom = new DOMDocument(); | ||||
| $xunit_dom->loadXML(Filesystem::readFile($xunit_tmp)); | $xunit_dom->loadXML(Filesystem::readFile($xunit_tmp)); | ||||
| $results = array(); | $results = array(); | ||||
| ▲ Show 20 Lines • Show All 46 Lines • Show Last 20 Lines | |||||