jest-phabricator
TypeScript icon, indicating that this package has built-in type declarations

29.7.0 • Public • Published

jest-phabricator

This Repo contains the testResultsProcessor needed to create the coverage map needed by Phabricator to show inline coverage at diff time.

How to use it

Below you'll find an example of a Phabricator Jest UnitTestEngine reference implementation.

You need to add the jest unit engine to your .arcconfig:

...

"unit.engine" : "JestUnitTestEngine",

...

Or use the ArcanistConfigurationDrivenUnitTestEngine and add an entry to your .arcunit

"jest": {
  "type": "jest",
  "include": "(\\.js$)"
}

In JestUnitTestEngine there are a couple of constants you probably need to modify:

  • PROCESSOR points to the path or the processor
  • JEST_PATH is the path to Jest

If you need to pass to Jest a custom configuration you can either use JEST_PATH and point it to a bash/script file that will just jest with --config=path/to/config or alternatively you can add the config option in the getJestOptions php function.

Reference implementation

final class JestUnitTestEngine extends ArcanistUnitTestEngine {
  const PROCESSOR = 'jest/packages/jest-phabricator/build/index.js';
  const JEST_PATH = 'jest/packages/jest/bin/jest.js';
  const TOO_MANY_FILES_TO_COVER = 100;
  const GIGANTIC_DIFF_THRESHOLD = 200;

  public function getEngineConfigurationName() {
    return 'jest';
  }

  private function getRoot() {
    return $this->getWorkingCopy()->getProjectRoot();
  }

  private function getOutputJSON() {
    return $this->getRoot() . '/output.json';
  }

  private function getFutureResults($future) {
    list($stdout, $stderr) = $future->resolvex();
    $output_JSON = $this->getOutputJSON();
    $report_path_exists = file_exists($output_JSON);
    $raw_results = null;

    if ($report_path_exists) {
      $raw_results = json_decode(
        Filesystem::readFile($output_JSON),
        true
      )['coverageMap'];
      Filesystem::remove($output_JSON);
    } else {
      $raw_results = json_decode($stdout, true);
    }

    if (!is_array($raw_results)) {
      throw new Exception("Unit test script emitted invalid JSON: {$stdout}");
    }

    $results = array();
    foreach ($raw_results as $result) {
      $test_result = new ArcanistUnitTestResult();
      $test_result->setName($result['name']);
      $succeed = isset($result['status']) && $result['status'] == 'passed';
      $test_result->setResult(
        $succeed ?
        ArcanistUnitTestResult::RESULT_PASS :
        ArcanistUnitTestResult::RESULT_FAIL
      );

      if (isset($result['coverage'])) {
        $coverage = array();
        $root = $this->getRoot() . '/';
        foreach ($result['coverage'] as $file_path => $coverage_data) {
          if (substr($file_path, 0, strlen($root)) == $root) {
            $file_path = substr($file_path, strlen($root));
          }
          $coverage[$file_path] = $coverage_data;
       }
       $test_result->setCoverage($coverage);
     }
     $test_result->setUserData($result['message']);
     $results[] = $test_result;
   }

    return $results;
  }

  private function runCommands($commands) {
    $futures = array();
    foreach ($commands as $command) {
      $bin = $command['bin'];
      $options = implode(' ', $command['options']);
      $paths = $command['paths'];
      $futures[] = new ExecFuture("{$bin} {$options} %Ls", $paths);
    }

    $console = PhutilConsole::getConsole();

    // Pass stderr through so we can give the user updates on test
    // status as tests run.
    $completed = array();
    $iterator = new FutureIterator($futures);
    foreach ($iterator->setUpdateInterval(0.2) as $_) {
      foreach ($futures as $key => $future) {
        if (isset($completed[$key])) {
          continue;
        }
        if ($future->isReady()) {
          $completed[$key] = true;
        }
        list(, $stderr) = $future->read();
        $console->writeErr('%s', $stderr);
        break;
      }
    }
    // Finish printing output for remaining futures
    foreach ($futures as $key => $future) {
      if (!isset($completed[$key])) {
        list(, $stderr) = $future->read();
        $console->writeErr('%s', $stderr);
      }
    }
    $results = array();
    foreach ($futures as $future) {
      $results[] = $this->getFutureResults($future);
    }

    if (empty($results)) {
      return array();
    }
    return call_user_func_array('array_merge', $results);
  }

  private function runJSTests() {
    $console = PhutilConsole::getConsole();
    $root = $this->getRoot();

    $result_arrays = [];
    $paths = $this->getPaths();
    $jest_paths = array();
    foreach ($paths as $path) {
      $ext = idx(pathinfo($path), 'extension');
      if ($ext === 'js' || $ext === 'json') {
        // Filter deleted modules because Jest can't do anything with them.
        if (file_exists("$root/$path")) {
          $jest_paths[] = "$root/$path";
        }
      }
    }

    $commands = [];
    if (count($jest_paths) > self::GIGANTIC_DIFF_THRESHOLD) {
      $console->writeOut("Too many files, skipping JavaScript tests.\n");
      $result_arrays[] = array();
    } else {
      if (count($jest_paths) > 0) {
        $console->writeOut("Running JavaScript tests.\n");
        $commands[] = array(
          'bin' => self::JEST_PATH,
          'options' => $this->getJestOptions($jest_paths),
          'paths' => $jest_paths,
        );
      }

      try {
        $result_arrays[] = $this->runCommands($commands);
      } catch (Exception $e) {
        // Ignore the exception in case of failing tests
        // As Jest should have already printed the results.
        $result = new ArcanistUnitTestResult();
        $result->setName('JavaScript tests');
        $result->setResult(ArcanistUnitTestResult::RESULT_FAIL);
        $result->setDuration(0);
        $result_arrays[] = array($result);
      }
    }

    $console->writeOut("Finished tests.\n");
    // $console->writeErr(implode(', ', $result_arrays));
    return call_user_func_array('array_merge', $result_arrays);
  }

  private function getJestOptions($paths) {
    $output_JSON = $this->getOutputJSON();
    $options = array(
      '--colors',
      '--json',
      '--passWithNoTests true',
      '--outputFile=' . $output_JSON,
      '--testResultsProcessor=' . self::PROCESSOR
    );

    // Checks for the number of files to cover, in case it's too big skips coverage
    // A better solution would involve knowing what's the machine buffer size limit
    // for exec and check if the command can stay within it.
    if (count($paths) < self::TOO_MANY_FILES_TO_COVER) {
      $options[] = '--findRelatedTests ' . join(' ', $paths);
      $options[] = '--coverage';
      $options[] = '--collectCoverageFrom '. join(' ', $paths);
    }

    return $options;
  }

  /** @Override */
  public function run() {
    return self::runJSTests();
  }
}

Versions

Current Tags

VersionDownloads (Last 7 Days)Tag
29.7.0565latest
30.0.0-alpha.60next

Version History

VersionDownloads (Last 7 Days)Published
30.0.0-alpha.60
30.0.0-alpha.50
30.0.0-alpha.40
30.0.0-alpha.30
30.0.0-alpha.20
30.0.0-alpha.11
29.7.0565
29.6.40
29.6.30
29.6.20
29.6.10
29.6.00
29.5.00
29.4.30
29.4.20
29.4.10
29.4.00
29.3.10
29.2.10
29.2.03
29.1.20
29.1.00
29.0.30
29.0.20
29.0.10
29.0.00
29.0.0-alpha.60
29.0.0-alpha.40
29.0.0-alpha.30
29.0.0-alpha.10
29.0.0-alpha.00
28.1.3179
28.1.10
28.1.00
28.0.20
28.0.10
28.0.00
28.0.0-alpha.90
28.0.0-alpha.80
28.0.0-alpha.70
28.0.0-alpha.60
28.0.0-alpha.50
28.0.0-alpha.40
28.0.0-alpha.30
28.0.0-alpha.20
28.0.0-alpha.13
28.0.0-alpha.00
27.5.13
27.5.00
27.4.60
27.4.20
27.4.10
27.4.00
27.3.10
27.3.00
27.2.50
27.2.40
27.2.31
27.2.23
27.2.00
27.1.10
27.1.00
27.0.60
27.0.27
27.0.11
27.0.00
27.0.0-next.111
27.0.0-next.1010
27.0.0-next.90
27.0.0-next.88
27.0.0-next.78
27.0.0-next.60
27.0.0-next.50
27.0.0-next.30
27.0.0-next.10
27.0.0-next.00
26.6.2141
26.6.11
26.6.00
26.5.21
26.5.00
26.3.00
26.2.00
26.1.031
26.0.10
26.0.1-alpha.00
26.0.00
26.0.0-alpha.20
26.0.0-alpha.10
26.0.0-alpha.00
25.5.00
25.4.00
25.3.00
25.2.63
25.2.40
25.2.30
25.2.10
25.2.1-alpha.20
25.2.1-alpha.11
25.2.01
25.2.0-alpha.861
25.1.00
25.0.00
24.9.01,261
24.8.01
24.7.1292
24.7.01
24.6.00
24.5.00
24.3.021
24.2.0-alpha.00
24.0.00
24.0.0-alpha.160
24.0.0-alpha.150
24.0.0-alpha.130
24.0.0-alpha.121
24.0.0-alpha.111
24.0.0-alpha.100
24.0.0-alpha.90
24.0.0-alpha.70
24.0.0-alpha.60
24.0.0-alpha.50
24.0.0-alpha.41
24.0.0-alpha.20
24.0.0-alpha.10
24.0.0-alpha.00
23.2.00
23.0.10
22.4.30
22.2.00
22.1.00
22.0.60
22.0.30
22.0.20
22.0.10
22.0.00
21.3.0-beta.150
21.3.0-beta.140
21.3.0-beta.130
21.3.0-beta.120
21.3.0-beta.110
21.3.0-beta.100
21.3.0-beta.90
21.3.0-alpha.eff7a1cf0
21.3.0-alpha.1e3ee68e0
21.2.02
21.0.20
21.0.00
21.0.0-beta.10
21.0.0-alpha.20
21.0.0-alpha.10
20.1.0-echo.10
20.1.0-delta.50
20.1.0-delta.40
20.1.0-delta.30
20.1.0-delta.20
20.1.0-delta.10
20.1.0-chi.10
20.1.0-beta.10
20.1.0-alpha.30
20.1.0-alpha.20
20.1.0-alpha.10
20.0.3432
20.0.20
20.0.10
20.0.00
19.3.0-alpha.854022540
19.2.0-alpha.993e64af0
19.1.0-alpha.eed820340
19.0.20
19.0.10
19.0.00
18.5.0-alpha.7da3df390
18.1.00
18.0.00
0.0.00

Package Sidebar

Install

npm i jest-phabricator

Weekly Downloads

2,986

Version

29.7.0

License

MIT

Unpacked Size

10.1 kB

Total Files

5

Last publish

Collaborators

  • simenb
  • cpojer
  • aaronabramov
  • openjs-operations