/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

import type {EnvironmentClass} from 'types/Environment';
import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
import type {Resolver} from 'types/Resolve';
import type {TestFramework} from 'types/TestRunner';
import type {TestResult} from 'types/TestResult';
import type RuntimeClass from 'jest-runtime';

import fs from 'fs';
import {
  BufferedConsole,
  Console,
  NullConsole,
  getConsoleOutput,
  setGlobal,
} from 'jest-util';
import jasmine2 from 'jest-jasmine2';
import {getTestEnvironment} from 'jest-config';
import docblock from 'jest-docblock';

// The default jest-runner is required because it is the default test runner
// and required implicitly through the `testRunner` ProjectConfig option.
jasmine2;

export default function runTest(
  path: Path,
  globalConfig: GlobalConfig,
  config: ProjectConfig,
  resolver: Resolver,
) {
  let testSource;

  try {
    testSource = fs.readFileSync(path, 'utf8');
  } catch (e) {
    return Promise.reject(e);
  }

  const parsedDocblock = docblock.parse(docblock.extract(testSource));
  const customEnvironment = parsedDocblock['jest-environment'];
  let testEnvironment = config.testEnvironment;

  if (customEnvironment) {
    testEnvironment = getTestEnvironment(
      Object.assign({}, config, {
        testEnvironment: customEnvironment,
      }),
    );
  }

  /* $FlowFixMe */
  const TestEnvironment = (require(testEnvironment): EnvironmentClass);
  /* $FlowFixMe */
  const testFramework = (require(config.testRunner): TestFramework);
  /* $FlowFixMe */
  const Runtime = (require(config.moduleLoader || 'jest-runtime'): Class<
    RuntimeClass,
  >);

  const environment = new TestEnvironment(config);
  const consoleOut = globalConfig.useStderr ? process.stderr : process.stdout;
  const consoleFormatter = (type, message) =>
    getConsoleOutput(
      config.cwd,
      !!globalConfig.verbose,
      // 4 = the console call is buried 4 stack frames deep
      BufferedConsole.write([], type, message, 4),
    );

  let testConsole;
  if (globalConfig.verbose) {
    testConsole = new Console(consoleOut, process.stderr, consoleFormatter);
  } else {
    if (globalConfig.silent) {
      testConsole = new NullConsole(
        consoleOut,
        process.stderr,
        consoleFormatter,
      );
    } else {
      testConsole = new BufferedConsole();
    }
  }

  const cacheFS = {[path]: testSource};
  setGlobal(environment.global, 'console', testConsole);
  const runtime = new Runtime(config, environment, resolver, cacheFS, {
    collectCoverage: globalConfig.collectCoverage,
    collectCoverageFrom: globalConfig.collectCoverageFrom,
    collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom,
    mapCoverage: globalConfig.mapCoverage,
  });
  const start = Date.now();
  return testFramework(globalConfig, config, environment, runtime, path)
    .then((result: TestResult) => {
      const testCount =
        result.numPassingTests +
        result.numFailingTests +
        result.numPendingTests;
      result.perfStats = {end: Date.now(), start};
      result.testFilePath = path;
      result.coverage = runtime.getAllCoverageInfo();
      result.sourceMaps = runtime.getSourceMapInfo();
      result.console = testConsole.getBuffer();
      result.skipped = testCount === result.numPendingTests;
      result.displayName = config.displayName;
      return result;
    })
    .then(
      result =>
        Promise.resolve().then(() => {
          environment.dispose();
          if (globalConfig.logHeapUsage) {
            if (global.gc) {
              global.gc();
            }
            result.memoryUsage = process.memoryUsage().heapUsed;
          }

          // Delay the resolution to allow log messages to be output.
          return new Promise(resolve => setImmediate(() => resolve(result)));
        }),
      err =>
        Promise.resolve().then(() => {
          environment.dispose();
          throw err;
        }),
    );
}

@@ -2355,23 +2355,22 @@
 lConfig.
-verbose
+silent
 ) %7B%0A    
@@ -2387,24 +2387,28 @@
 e = new 
+Null
 Console(
 consoleO
@@ -2399,16 +2399,23 @@
 Console(
+%0A      
 consoleO
@@ -2417,16 +2417,22 @@
 soleOut,
+%0A     
  process
@@ -2439,16 +2439,22 @@
 .stderr,
+%0A     
  console
@@ -2462,16 +2462,22 @@
 ormatter
+,%0A    
 );%0A  %7D e
@@ -2495,38 +2495,39 @@
 f (globalConfig.
-silent
+verbose
 ) %7B%0A      testCo
@@ -2534,36 +2534,32 @@
 nsole = new 
-Null
 Console(
 %0A        con
@@ -2546,25 +2546,16 @@
 Console(
-%0A        
 consoleO
@@ -2557,24 +2557,16 @@
 soleOut,
-%0A       
  process
@@ -2573,24 +2573,16 @@
 .stderr,
-%0A       
  console
@@ -2590,24 +2590,16 @@
 ormatter
-,%0A      
 );%0A    %7D

Fix `--silent --verbose` problem (#4505)