// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.testing.PerformanceTimerTest');
goog.setTestOnly('goog.testing.PerformanceTimerTest');

goog.require('goog.async.Deferred');
goog.require('goog.dom');
goog.require('goog.math');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PerformanceTimer');
goog.require('goog.testing.jsunit');

var mockClock;
var sandbox;
var timer;
function setUpPage() {
  sandbox = document.getElementById('sandbox');
}

function setUp() {
  mockClock = new goog.testing.MockClock(true);
  timer = new goog.testing.PerformanceTimer();
}

function tearDown() {
  mockClock.dispose();
  timer = null;
  goog.dom.removeChildren(sandbox);
}

function testConstructor() {
  assertTrue(
      'Timer must be an instance of goog.testing.PerformanceTimer',
      timer instanceof goog.testing.PerformanceTimer);
  assertEquals(
      'Timer must collect the default number of samples', 10,
      timer.getNumSamples());
  assertEquals(
      'Timer must have the default timeout interval', 5000,
      timer.getTimeoutInterval());
}

function testRun_noSetUpOrTearDown() {
  runAndAssert(false, false, false);
}

function testRun_withSetup() {
  runAndAssert(true, false, false);
}

function testRun_withTearDown() {
  runAndAssert(false, true, false);
}

function testRun_withSetUpAndTearDown() {
  runAndAssert(true, true, false);
}

function testRunAsync_noSetUpOrTearDown() {
  runAndAssert(false, false, true);
}

function testRunAsync_withSetup() {
  runAndAssert(true, false, true);
}

function testRunAsync_withTearDown() {
  runAndAssert(false, true, true);
}

function testRunAsync_withSetUpAndTearDown() {
  runAndAssert(true, true, true);
}

/**
 * @param {boolean} useSetUp
 * @param {boolean} useTearDown
 * @param {boolean} runAsync
 */
function runAndAssert(useSetUp, useTearDown, runAsync) {
  var fakeExecutionTime = [100, 95, 98, 104, 130, 101, 96, 98, 90, 103];
  var count = 0;
  var testFunction = function() {
    mockClock.tick(fakeExecutionTime[count++]);
    if (runAsync) {
      var deferred = new goog.async.Deferred();
      deferred.callback();
      return deferred;
    }
  };

  var setUpCount = 0;
  var setUpFunction = function() {
    // Should have no effect on total time.
    mockClock.tick(7);
    setUpCount++;
    if (runAsync) {
      var deferred = new goog.async.Deferred();
      deferred.callback();
      return deferred;
    }
  };

  var tearDownCount = 0;
  var tearDownFunction = function() {
    // Should have no effect on total time.
    mockClock.tick(11);
    tearDownCount++;
    if (runAsync) {
      var deferred = new goog.async.Deferred();
      deferred.callback();
      return deferred;
    }
  };

  // Fast test function should complete successfully in under 5 seconds...
  var task = new goog.testing.PerformanceTimer.Task(testFunction);
  if (useSetUp) {
    task.withSetUp(setUpFunction);
  }
  if (useTearDown) {
    task.withTearDown(tearDownFunction);
  }
  if (runAsync) {
    var assertsRan = false;
    var deferred = timer.runAsyncTask(task);
    deferred.addCallback(function(results) {
      assertsRan = assertResults(
          results, useSetUp, useTearDown, setUpCount, tearDownCount,
          fakeExecutionTime);
    });
    assertTrue(assertsRan);
  } else {
    var results = timer.runTask(task);
    assertResults(
        results, useSetUp, useTearDown, setUpCount, tearDownCount,
        fakeExecutionTime);
  }
}

/**
 * @param {Array} results
 * @param {boolean} useSetUp
 * @param {boolean} useTearDown
 * @param {boolean} setUpCount
 * @param {boolean} tearDownCount
 * @param {boolean} fakeExecutionTime
 * @return {boolean} true
 */
function assertResults(
    results, useSetUp, useTearDown, setUpCount, tearDownCount,
    fakeExecutionTime) {
  assertNotNull('Results must be available.', results);

  assertEquals(
      'Average is wrong.', goog.math.average.apply(null, fakeExecutionTime),
      results['average']);
  assertEquals(
      'Standard deviation is wrong.',
      goog.math.standardDeviation.apply(null, fakeExecutionTime),
      results['standardDeviation']);

  assertEquals('Count must be as expected.', 10, results['count']);
  assertEquals('Maximum is wrong.', 130, results['maximum']);
  assertEquals('Mimimum is wrong.', 90, results['minimum']);
  assertEquals(
      'Total must be a nonnegative number.',
      goog.math.sum.apply(null, fakeExecutionTime), results['total']);

  assertEquals(
      'Set up count must be as expected.', useSetUp ? 10 : 0, setUpCount);
  assertEquals(
      'Tear down count must be as expected.', useTearDown ? 10 : 0,
      tearDownCount);

  return true;
}

function testTimeout() {
  var count = 0;
  var testFunction = function() {
    mockClock.tick(100);
    ++count;
  };

  timer.setNumSamples(200);
  timer.setTimeoutInterval(2500);
  var results = timer.run(testFunction);

  assertNotNull('Results must be available', results);
  assertEquals('Count is wrong', count, results['count']);
  assertTrue(
      'Count must less than expected',
      results['count'] < timer.getNumSamples());
}

function testCreateResults() {
  var samples = [53, 0, 103];
  var expectedResults = {
    'average': 52,
    'count': 3,
    'median': 53,
    'maximum': 103,
    'minimum': 0,
    'standardDeviation': goog.math.standardDeviation.apply(null, samples),
    'total': 156
  };
  assertObjectEquals(
      expectedResults, goog.testing.PerformanceTimer.createResults(samples));
}
