// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax --no-always-opt --opt
// Flags: --no-stress-flush-bytecode
// Flags: --no-stress-incremental-marking

var source =
`
function fib(x) {
  if (x < 2) return 1;
  return fib(x-1) + fib(x-2);
}
function is_optimized(f) {
  return (%GetOptimizationStatus(f) & 16) ? "optimized" : "unoptimized";
}
(function iife() {
  return 1;
})();
fib(5);
`;

var break_source =
`
function g() {
  debugger;
}
function f(x) {
  if (x == 0) g();
  else f(x - 1);
}
function h() {
  g();
}
f(3);
`;

var nested =
`
var f = (function outer() {
  function nested_0() {
    return function nested_1() {
      return function nested_2() {
        return function nested_3() {}
      }
    }
  }
  function nested_4() {}
  return nested_0();
})();
f()()();
`;

let {session, contextGroup, Protocol} = InspectorTest.start("Test collecting code coverage data with Profiler.collectCoverage.");

function ClearAndGC() {
  return Protocol.Runtime.evaluate({ expression: "fib = g = f = h = is_optimized = null;" })
             .then(GC);
}

function GC() {
  return Protocol.HeapProfiler.collectGarbage();
}

function LogSorted(message) {
  message.result.result.sort((a, b) => parseInt(a.scriptId) - parseInt(b.scriptId));
  return InspectorTest.logMessage(message);
}

InspectorTest.runTestSuite([
  function testPreciseCountBaseline(next)
  {
    Protocol.Runtime.enable()
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(GC)
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({callCount: true, detailed: false}))
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testPreciseCountCoverage(next)
  {
    Protocol.Runtime.enable()
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({callCount: true, detailed: false}))
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(ClearAndGC)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testPreciseCoverageFail(next)
  {
    Protocol.Runtime.enable()
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(ClearAndGC)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(InspectorTest.logMessage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testBestEffortCoverage(next)
  {
    Protocol.Runtime.enable()
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(ClearAndGC)
      .then(Protocol.Profiler.getBestEffortCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.getBestEffortCoverage)
      .then(LogSorted)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testBestEffortCoverageWithPreciseBinaryEnabled(next)
  {
    Protocol.Runtime.enable()
    .then(Protocol.Profiler.enable)
    .then(() => Protocol.Profiler.startPreciseCoverage({detailed: false}))
    .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
    .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
    .then(InspectorTest.logMessage)
    .then(ClearAndGC)
    .then(Protocol.Profiler.getBestEffortCoverage)
    .then(LogSorted)
    .then(Protocol.Profiler.getBestEffortCoverage)
    .then(LogSorted)
    .then(ClearAndGC)
    .then(Protocol.Profiler.stopPreciseCoverage)
    .then(Protocol.Profiler.disable)
    .then(Protocol.Runtime.disable)
    .then(ClearAndGC)
    .then(next);
  },
  function testBestEffortCoverageWithPreciseCountEnabled(next)
  {
    Protocol.Runtime.enable()
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({callCount: true, detailed: false}))
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(ClearAndGC)
      .then(Protocol.Profiler.getBestEffortCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.getBestEffortCoverage)
      .then(LogSorted)
      .then(ClearAndGC)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testEnablePreciseCountCoverageAtPause(next)
  {
    function handleDebuggerPause() {
      Protocol.Profiler.enable()
          .then(() => Protocol.Profiler.startPreciseCoverage({callCount: true, detailed: false}))
          .then(Protocol.Debugger.resume)
    }
    Protocol.Debugger.enable();
    Protocol.Debugger.oncePaused().then(handleDebuggerPause);
    Protocol.Runtime.enable()
      .then(() => Protocol.Runtime.compileScript({ expression: break_source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(ClearAndGC)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(ClearAndGC)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(Protocol.Debugger.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testPreciseBinaryCoverage(next)
  {
    Protocol.Runtime.enable()
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({detailed: false}))
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(() => Protocol.Runtime.evaluate({ expression: "is_optimized(fib)" }))
      .then(message => InspectorTest.logMessage(message))
      .then(() => Protocol.Runtime.evaluate({ expression: "fib(20)" }))
      .then(message => InspectorTest.logMessage(message))
      .then(() => Protocol.Runtime.evaluate({ expression: "is_optimized(fib)" }))
      .then(message => InspectorTest.logMessage(message))
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testPreciseEmptyScriptCoverageEntries(next)
  {
    // Enabling the debugger holds onto script objects even though its
    // functions can be garbage collected. We would get empty ScriptCoverage
    // entries unless we remove them.
    Protocol.Debugger.enable()
      .then(Protocol.Runtime.enable)
      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(ClearAndGC)
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({detailed: false}))
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(Protocol.Debugger.disable)
      .then(ClearAndGC)
      .then(next);
  },
  function testPreciseCountCoveragePartial(next)
  {
    Protocol.Runtime.enable()
      .then(Protocol.Profiler.enable)
      .then(() => Protocol.Profiler.startPreciseCoverage({callCount: true, detailed: false}))
      .then(() => Protocol.Runtime.compileScript({ expression: nested, sourceURL: arguments.callee.name, persistScript: true }))
      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
      .then(InspectorTest.logMessage)
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(() => Protocol.Runtime.evaluate({ expression: "f()" }))
      .then(Protocol.Profiler.takePreciseCoverage)
      .then(LogSorted)
      .then(Protocol.Profiler.stopPreciseCoverage)
      .then(Protocol.Profiler.disable)
      .then(Protocol.Runtime.disable)
      .then(ClearAndGC)
      .then(next);
  },
]);
