text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  (async function() {
    TestRunner.addResult(`This test verifies throttler behavior.\n`);
  
    class TimeoutMock {
      constructor() {
        this._timeoutId = 0;
        this._timeoutIdToProcess = new Map();
        this._timeoutIdToMillis = new Map();
        this._time = 1;
        this.setTimeout = this.setTimeout.bind(this);
        this.clearTimeout = this.clearTimeout.bind(this);
        this.getTime = this.getTime.bind(this);
      }
  
      /**
       * @param {!Function} operation
       * @param {number} timeout
       */
      setTimeout(operation, timeout) {
        this._timeoutIdToProcess.set(++this._timeoutId, operation);
        this._timeoutIdToMillis.set(this._timeoutId, timeout);
        return this._timeoutId;
      }
  
      /**
       *
       * @param {number} timeoutId
       */
      clearTimeout(timeoutId) {
        this._timeoutIdToProcess.delete(timeoutId);
        this._timeoutIdToMillis.delete(timeoutId);
      }
  
      /**
       * @return {!Array<number>}
       */
      activeTimersTimeouts() {
        return Array.from(this._timeoutIdToMillis.values());
      }
  
      getTime() {
        return this._time;
      }
  
      fireAllTimers() {
        this._time = Math.max(...this.activeTimersTimeouts()) + 1;
        for (const timeoutId of this._timeoutIdToProcess.keys())
          this._timeoutIdToProcess.get(timeoutId).call(window);
        this._timeoutIdToProcess.clear();
        this._timeoutIdToMillis.clear();
      }
    }
  
    class ProcessMock {
      constructor(name, runnable) {
        this._runnable = runnable;
        this.processName = name;
        this.run = this.run.bind(this);
        this.run.processName = name;
  
        this.startPromise = new Promise(fulfill => this._startCallback = fulfill);
        this.finishPromise = new Promise(fulfill => this._finishCallback = fulfill);
      }
  
      run() {
        TestRunner.addResult('Process \'' + this.processName + '\' STARTED.');
        this._startCallback();
        if (this._runnable)
          this._runnable.call(null);
        return this.finishPromise;
      }
  
      finish() {
        this.startPromise.then(onFinish.bind(this));
  
        function onFinish() {
          TestRunner.addResult('Process \'' + this.processName + '\' FINISHED.');
          this._finishCallback();
        }
      }
  
      static create(name, runnable) {
        return new ProcessMock(name, runnable);
      }
    }
  
    var throttler = new Common.Throttler(1989);
    var timeoutMock = new TimeoutMock();
    throttler._setTimeout = timeoutMock.setTimeout;
    throttler._clearTimeout = timeoutMock.clearTimeout;
    throttler._getTime = timeoutMock.getTime;
    TestRunner.addSniffer(throttler, 'schedule', logSchedule, true);
  
    function testSimpleSchedule(next, runningProcess) {
      assertThrottlerIdle();
      throttler.schedule(ProcessMock.create('operation #1').run, false);
      var process = ProcessMock.create('operation #2');
      throttler.schedule(process.run);
  
      var promise = Promise.resolve();
      if (runningProcess) {
        runningProcess.finish();
        promise = waitForProcessFinish();
      }
  
      promise.then(() => {
        assertThrottlerTimeout();
        timeoutMock.fireAllTimers();
        process.finish();
        return waitForProcessFinish();
      }).then(next);
    }
  
    function testAsSoonAsPossibleOverrideTimeout(next, runningProcess) {
      assertThrottlerIdle();
      throttler.schedule(ProcessMock.create('operation #1').run);
      var process = ProcessMock.create('operation #2');
      throttler.schedule(process.run, true);
  
      var promise = Promise.resolve();
      if (runningProcess) {
        runningProcess.finish();
        promise = waitForProcessFinish();
      }
  
      promise
          .then(function() {
            assertThrottlerTimeout();
            timeoutMock.fireAllTimers();
            process.finish();
            return waitForProcessFinish();
          })
          .then(next);
    }
  
    function testAlwaysExecuteLastScheduled(next, runningProcess) {
      assertThrottlerIdle();
      var process = null;
      for (var i = 0; i < 4; ++i) {
        process = ProcessMock.create('operation #' + i);
        throttler.schedule(process.run, i % 2 === 0);
      }
      var promise = Promise.resolve();
      if (runningProcess) {
        runningProcess.finish();
        promise = waitForProcessFinish();
      }
      promise
          .then(function() {
            assertThrottlerTimeout();
            timeoutMock.fireAllTimers();
            process.finish();
            return waitForProcessFinish();
          })
          .then(next);
    }
  
    TestRunner.runTestSuite([
      testSimpleSchedule,
  
      testAsSoonAsPossibleOverrideTimeout,
  
      testAlwaysExecuteLastScheduled,
  
      function testSimpleScheduleDuringProcess(next) {
        var runningProcess = throttlerToRunningState();
        runningProcess.startPromise.then(function() {
          testSimpleSchedule(next, runningProcess);
        });
      },
  
      function testAsSoonAsPossibleOverrideDuringProcess(next) {
        var runningProcess = throttlerToRunningState();
        runningProcess.startPromise.then(function() {
          testAsSoonAsPossibleOverrideTimeout(next, runningProcess);
        });
      },
  
      function testAlwaysExecuteLastScheduledDuringProcess(next) {
        var runningProcess = throttlerToRunningState();
        runningProcess.startPromise.then(function() {
          testAlwaysExecuteLastScheduled(next, runningProcess);
        });
      },
  
      function testScheduleFromProcess(next) {
        var nextProcess;
        assertThrottlerIdle();
        var process = ProcessMock.create('operation #1', processBody);
        throttler.schedule(process.run);
        assertThrottlerTimeout();
        timeoutMock.fireAllTimers();
        process.finish();
        waitForProcessFinish()
            .then(function() {
              assertThrottlerTimeout();
              timeoutMock.fireAllTimers();
              nextProcess.finish();
              return waitForProcessFinish();
            })
            .then(next);
  
        function processBody() {
          nextProcess = ProcessMock.create('operation #2');
          throttler.schedule(nextProcess.run, false);
        }
      },
  
      function testExceptionFromProcess(next) {
        var process = ProcessMock.create('operation #1', processBody);
        throttler.schedule(process.run);
        timeoutMock.fireAllTimers();
        waitForProcessFinish().then(function() {
          assertThrottlerIdle();
          next();
        });
  
        function processBody() {
          throw new Error('Exception during process execution.');
        }
      },
  
      async function testPromise(next) {
        var process = ProcessMock.create('operation #1', () => 1);
        var schedulePromse = throttler.schedule(process.run).then(() => TestRunner.addResult('The promise resolved.'));
        await Promise.resolve();
        timeoutMock.fireAllTimers();
        await Promise.resolve();
        process.finish();
  
        await schedulePromse;
        next();
  
      }
    ]);
  
    function waitForProcessFinish() {
      var promiseResolve;
      var hasFinished;
      TestRunner.addSniffer(Common.Throttler.prototype, '_processCompletedForTests', onFinished);
      function onFinished() {
        hasFinished = true;
        if (promiseResolve)
          promiseResolve();
      }
      return new Promise(function(success) {
        promiseResolve = success;
        if (hasFinished)
          success();
      });
    }
  
    function throttlerToRunningState() {
      assertThrottlerIdle();
      var process = ProcessMock.create('long operation');
      throttler.schedule(process.run);
      assertThrottlerTimeout();
      timeoutMock.fireAllTimers();
      return process;
    }
  
    function assertThrottlerIdle() {
      var timeouts = timeoutMock.activeTimersTimeouts();
      if (timeouts.length !== 0) {
        TestRunner.addResult(
            'ERROR: throttler is not in idle state. Scheduled timers timeouts: [' + timeouts.sort().join(', ') + ']');
        TestRunner.completeTest();
        return;
      }
      TestRunner.addResult('Throttler is in IDLE state (doesn\'t have any timers set up)');
    }
  
    function assertThrottlerTimeout() {
      var timeouts = timeoutMock.activeTimersTimeouts();
      if (timeouts.length === 0) {
        TestRunner.addResult('ERROR: throttler is not in timeout state. Scheduled timers timeouts are empty!');
        TestRunner.completeTest();
        return;
      }
      TestRunner.addResult(
          'Throttler is in TIMEOUT state. Scheduled timers timeouts: [' + timeouts.sort().join(', ') + ']');
    }
  
    function logSchedule(operation, asSoonAsPossible, returnValue) {
      if (returnValue === undefined) {
        returnValue = asSoonAsPossible;
        asSoonAsPossible = undefined;
      }
      TestRunner.addResult('SCHEDULED: \'' + operation.processName + '\' asSoonAsPossible: ' + asSoonAsPossible);
    }
  })();
  