// 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.

let {session, contextGroup, Protocol} = InspectorTest.start('Checks that we drop old async call chains.');

Protocol.Debugger.enable();
Protocol.Runtime.enable();
InspectorTest.runAsyncTestSuite([
  async function testInfrastructure() {
    Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainPromise(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainPromise(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainPromise(5, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainSetTimeout(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainSetTimeout(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(1024);
    runWithAsyncChainSetTimeout(5, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
  },

  async function testZeroLimit() {
    const limit = 0;
    Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
  },

  async function testOneLimit() {
    const limit = 1;
    Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
  },

  async function testTwoLimit() {
    const limit = 2;
    Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainPromise(3, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(1, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(2, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

    await setMaxAsyncTaskStacks(limit);
    runWithAsyncChainSetTimeout(3, 'console.trace(42)');
    dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
  },

  async function testMoreThanTwoLimit() {
    for (let limit = 3; limit <= 7; ++limit) {
      Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainPromise(1, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainPromise(2, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainPromise(3, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainSetTimeout(1, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainSetTimeout(2, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());

      await setMaxAsyncTaskStacks(limit);
      runWithAsyncChainSetTimeout(3, 'console.trace(42)');
      dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
    }
  },
]);

function runWithAsyncChainPromise(len, source) {
  InspectorTest.log(`Run expression '${source}' with async chain len: ${len}`);
  let asyncCall = `(function asyncCall(num) {
    if (num === 0) {
      ${source};
      return;
    }
    Promise.resolve().then(() => asyncCall(num - 1));
  })(${len})`;
  Protocol.Runtime.evaluate({expression: asyncCall});
}

function runWithAsyncChainSetTimeout(len, source) {
  InspectorTest.log(`Run expression '${source}' with async chain len: ${len}`);
  let setTimeoutPrefix = '() => setTimeout(';
  let setTimeoutSuffix = ', 0)';
  Protocol.Runtime.evaluate({
    expression: `setTimeout(${setTimeoutPrefix.repeat(len - 1)}'${source}'${setTimeoutSuffix.repeat(len - 1)}, 0)`
  });
}

function dumpAsyncChainLength(message) {
  let stackTrace = message.params.asyncStackTrace || message.params.stackTrace.parent;
  let asyncChainCount = 0;
  while (stackTrace) {
    ++asyncChainCount;
    stackTrace = stackTrace.parent;
  }
  InspectorTest.log(`actual async chain len: ${asyncChainCount}\n`);
}

async function setMaxAsyncTaskStacks(max) {
  let expression = `inspector.setMaxAsyncTaskStacks(${max})`;
  InspectorTest.log(expression);
  await Protocol.Runtime.evaluate({expression});
}
