// Copyright 2018 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('stepOut async function');

session.setupScriptMap();

Protocol.Runtime.enable();

InspectorTest.runAsyncTestSuite([
  async function testTrivial() {
    InspectorTest.log('Check that we have proper async stack at return');
    contextGroup.addInlineScript(`
      async function test() {
        await Promise.resolve();
        await foo();
      }

      async function foo() {
        await Promise.resolve();
        await bar();
      }

      async function bar() {
        await Promise.resolve();
        debugger;
    }`, 'testTrivial.js');
    await runTestAndStepAction('stepOut');
  },

  async function testStepOutPrecision() {
    InspectorTest.log('Check that stepOut go to resumed outer generator');
    contextGroup.addInlineScript(`
      function wait() {
        return new Promise(resolve => setTimeout(resolve, 0));
      }
      function floodWithTimeouts(a) {
        if (!a.stop)
          setTimeout(floodWithTimeouts.bind(this, a), 0);
      }

      async function test() {
        let a = {};
        floodWithTimeouts(a)
        await wait();
        await foo();
        await wait();
        a.stop = true;
      }

      async function foo() {
        await Promise.resolve();
        await bar();
        await wait();
      }

      async function bar() {
        await Promise.resolve();
        debugger;
        await wait();
      }`, 'testStepOutPrecision.js');
    await runTestAndStepAction('stepOut');
  },

  async function testStepIntoAtReturn() {
    InspectorTest.log('Check that stepInto at return go to resumed outer generator');
    contextGroup.addInlineScript(`
      function wait() {
        return new Promise(resolve => setTimeout(resolve, 0));
      }
      function floodWithTimeouts(a) {
        if (!a.stop)
          setTimeout(floodWithTimeouts.bind(this, a), 0);
      }

      async function test() {
        let a = {};
        floodWithTimeouts(a)
        await wait();
        await foo();
        a.stop = true;
      }

      async function foo() {
        await Promise.resolve();
        await bar();
      }

      async function bar() {
        await Promise.resolve();
        debugger;
      }`, 'testStepIntoAtReturn.js');
    await runTestAndStepAction('stepInto');
  },

  async function testStepOverAtReturn() {
    InspectorTest.log('Check that stepOver at return go to resumed outer generator');
    contextGroup.addInlineScript(`
      function wait() {
        return new Promise(resolve => setTimeout(resolve, 0));
      }
      function floodWithTimeouts(a) {
        if (!a.stop)
          setTimeout(floodWithTimeouts.bind(this, a), 0);
      }

      async function test() {
        let a = {};
        floodWithTimeouts(a)
        await wait();
        await foo();
        a.stop = true;
      }

      async function foo() {
        await Promise.resolve();
        await bar();
      }

      async function bar() {
        await Promise.resolve();
        debugger;
      }`, 'testStepIntoAtReturn.js');
    await runTestAndStepAction('stepOver');
  },

  async function testStepOutFromNotAwaitedCall() {
    InspectorTest.log('Checks stepOut from not awaited call');
    contextGroup.addInlineScript(`
      function wait() {
        return new Promise(resolve => setTimeout(resolve, 0));
      }
      function floodWithTimeouts(a) {
        if (!a.stop)
          setTimeout(floodWithTimeouts.bind(this, a), 0);
      }

      async function test() {
        let a = {};
        floodWithTimeouts(a)
        await wait();
        await foo();
        a.stop = true;
      }

      async function foo() {
        let a = {};
        floodWithTimeouts(a);
        await Promise.resolve();
        bar();
        a.stop = true;
      }

      async function bar() {
        await Promise.resolve();
        debugger;
      }`, 'testStepIntoAtReturn.js');
    await runTestAndStepAction('stepOut');
  }

]);

async function runTestAndStepAction(action) {
  Protocol.Debugger.enable();
  Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
  let finished =
      Protocol.Runtime.evaluate({expression: 'test()', awaitPromise: true})
          .then(() => false);
  while (true) {
    const r = await Promise.race([finished, waitPauseAndDumpStack()]);
    if (!r) break;
    Protocol.Debugger[action]();
  }
  await Protocol.Debugger.disable();
}

async function waitPauseAndDumpStack() {
  const {params} = await Protocol.Debugger.oncePaused();
  session.logCallFrames(params.callFrames);
  session.logAsyncStackTrace(params.asyncStackTrace);
  InspectorTest.log('');
  return true;
}