<commit_before><!DOCTYPE HTML>
<html>

<head>
  <title>Webkit Layout Test History</title>
  <style>
    body {
      font-family: arial;
      font-size: 13px;
    }
    h2 {
      font-size: 16px;
      margin-bottom: .25em;
    }
    h3 {
      font-size: 13px;
      margin: 0;
    }
    #max-results-form {
      display: inline;
    }
    #max-results-input {
      width: 30px;
    }
    #tests-form {
      display: -webkit-box;
    }
    #tests-form > * {
      display: -webkit-box;
    }
    #tests-form > div {
      -webkit-box-flex: 0;
    }
    #tests-input {
      -webkit-box-flex: 1;
    }
    .test-link {
      white-space: normal;
    }
    .test-link, .options-container {
      padding: 0 2px;
    }
    .test-table {
      white-space: nowrap;
    }
    .test-table {
      width: 100%;
    }
    .test-table tr {
      border: 1px solid red;
      background-color: #E8E8E8;
    }
    .test-table tbody tr:hover {
      opacity: .7;
    }
    .test-table th {
      -webkit-user-select: none;
      -moz-user-select: none;
    }
    .link, .sortable .header-text {
      color: blue;
      text-decoration: underline;
      cursor: pointer;
    }
    .table-header-content,
    .table-header-content * {
      display: -webkit-box;
    }
    .table-header-content * {
      -webkit-box-flex: 1;
      cursor: pointer;
      white-space: normal;
    }
    .results {
      cursor: pointer;
      padding: 0;
      font-size: 10px;
      text-align: center;
    }
    #legend {
      position: fixed;
      top: 5px;
      right: 5px;
      width: 400px;
      padding: 2px;
      border: 2px solid grey;
      background-color: white;
      z-index: 1;
    }
    #legend ul, #legend ol {
      margin-top: 0;
      margin-bottom: 5px;
    }
    #legend-contents * {
      margin: 3px 0;
      padding: 0 2px;
      float: left;
      border: 1px solid grey;
    }
    #builders * {
      margin: 0 5px;
      display: inline-block;
      white-space: nowrap;
    }
    .test-table .wrong-expectations,
    .wrong-expectations {
      background-color: #pink;
    }
    .P {
      background-color: #8fdf5f;
    }
    .N {
      background-color: #fff;
    }
    .C {
      background-color: #ffc343;
    }
    .T {
      background-color: #fffc6c;
    }
    .I {
      background-color: #69f;
    }
    .S {
      background-color: #c6c;
    }
    .F {
      background-color: #e98080;
    }
    .O {
      background-color: #8a7700;
    }
    .Z {
      background-color: #96f;
    }
    .merge {
      background-color: black;
      color: white;
    }
    table .merge {
      width: 1px;
    }
    .separator {
      border: 1px solid lightgray;
      height: 0px;
    }
    .different-platform {
      color: gray;
      font-size: 10px;
    }
    .current-builder {
      font-weight: bold;
    }
    #passing-tests {
      -webkit-column-count: 3;
      -webkit-column-gap: 25px;
      -webkit-column-rule: 1px dashed black;
      -moz-column-count: 3;
      -moz-column-gap: 25px;
      -moz-column-rule: 1px dashed black;
    }
    .not-found {
      color: red;
      font-size: large;
    }
    #loading-ui {
      position: fixed;
      top: 0;
      left: 0;
      background-color: yellow;
      padding: 5px;
      text-align: center;
      font-weight: bold;
    }
    #popup {
      background-color: white;
      z-index: 1;
      position: absolute;
      border: 3px solid grey;
      padding: 3px;
      -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
      -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
      -webkit-border-radius: 5px;
      -moz-border-radius: 5px;
    }
    #popup > ul {
      margin: 0;
      padding-left: 20px;
    }
    .expectations-container {
      clear: both;
    }
    .expectations-item {
      float: left;
      border: 1px solid grey;
    }
    .expectations-item .expectation {
      width: 400px;
      height: 300px;
      border: 0;
      border-top: 1px solid grey;
    }
    .expectations-item .large {
      width: 800px;
      height: 600px;
    }
    .expectations-item .checksum {
      height: 30px;
    }
    .used-platform {
      float: right;
      color: darkblue;
      margin: 0 5px;
    }
    .expectations-title {
      /* Hack to make a containing block for absolute positioned elements. */
      position: relative;
      overflow: hidden;
      clear: both;
    }
    .title {
      /* Position absolutely so the container does not grow to contain this. */
      position: absolute;
    }
    .platforms {
      position: absolute;
      background-color: white;
      right: 0;
      z-index: 1;
    }
  </style>

  <script src="dashboards/dashboard_base.js"></script>
  <script>
  /**
   * @fileoverview Creates a dashboard for multiple runs of a given set of tests
   * on the buildbots. Pulls in JSONP-ish files with the results for running
   * tests on a given builder (i.e. ADD_RESULTS(json_here)) and the expectations
   * for all tests on all builders (i.e. ADD_EXPECTATIONS(json_here)).
   *
   * This shows flakiness of the tests as well as runtimes for slow tests.
   *
   * Also, each column in the dashboard is sortable.
   *
   * Currently, only webkit tests are supported, but adding other test types
   * should just require the following steps:
   *   -generate results.json and expectations.json for these tests
   *   -copy them to the appropriate location
   *   -add the builder name to the list of builders below.
   */

  //////////////////////////////////////////////////////////////////////////////
  // CONSTANTS
  //////////////////////////////////////////////////////////////////////////////
  var ALL = 'ALL';
  var FORWARD = 'forward';
  var BACKWARD = 'backward';
  var CHROME_TEST_BASE_URL = 'http://src.chromium.org/viewvc/chrome/trunk/' +
      'src/webkit/data/layout_tests/platform/';
  var TEST_URL_BASE_PATH =
      'http://trac.webkit.org/projects/webkit/browser/trunk/LayoutTests/';
  var BUILDERS_BASE_PATH =
      'http://build.chromium.org/buildbot/waterfall/builders/';
  var TEST_RESULTS_BASE_PATH =
      'http://build.chromium.org/buildbot/layout_test_results/';
  var PLATFORMS = {
    'MAC': 'MAC',
    'LINUX': 'LINUX',
    'WIN': 'WIN',
    'WIN-XP': 'WIN-XP',
    'WIN-VISTA': 'WIN-VISTA'
  };
  var PLATFORM_FALLBACKS = {
    'WIN': 'ALL',
    'WIN-XP': 'WIN',
    'WIN-VISTA': 'WIN',
    'LINUX': 'ALL',
    'MAC': 'ALL'
  };
  var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'};
  var BASE_TABLE_HEADERS = ['bugs', 'modifiers', 'expectations', 'missing',
      'extra', 'slowest run', '% fail', 'flakiness (numbers are runtimes in seconds)'];
  var MIN_SECONDS_FOR_SLOW_TEST = 4;
  var MIN_SECONDS_FOR_SLOW_TEST_DEBUG = 2 * MIN_SECONDS_FOR_SLOW_TEST;
  var FAIL_RESULTS = ['IMAGE', 'IMAGE+TEXT', 'TEXT', 'SIMPLIFIED', 'MISSING'];
  var CHUNK_SIZE = 25;

  //////////////////////////////////////////////////////////////////////////////
  // Methods and objects from dashboard_base.js to override.
  //////////////////////////////////////////////////////////////////////////////
  function generatePage() {
    if (currentState.useTestData)
      return;

    document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';

    if (currentState.tests) {
      generatePageForIndividualTests(getIndividualTests());
    } else if (currentState.expectationsUpdate) {
      generatePageForExpectationsUpdate();
    } else {
      createTableHeadersArray('test');
      generatePageForBuilder(currentState.builder);
    }

    var maxResultsInput = $('max-results-input');
    if (maxResultsInput)
      maxResultsInput.value = getMaxResults();

    for (var builder in builders) {
      processTestResultsForBuilderAsync(builder);
    }
  }

  function handleValidHashParameter(key, value) {
    switch(key) {
      case 'tests':
        validateParameter(currentState, key, value,
            function() {
              return isValidName(value);
            });

        return true;

      case 'builder':
        validateParameter(currentState, key, value,
            function() {
              return value in builders;
            });

        return true;

      case 'sortColumn':
        validateParameter(currentState, key, value,
            function() {
              for (var i = 0; i < BASE_TABLE_HEADERS.length; i++) {
                if (value ==
                        getSortColumnFromTableHeader(BASE_TABLE_HEADERS[i]))
                  return true;
              }
              return value == 'test' || value == 'builder';
            });

        return true;

      case 'sortOrder':
        validateParameter(currentState, key, value,
            function() {
              return value == FORWARD || value == BACKWARD;
            });

        return true;

      case 'maxResults':
      case 'updateIndex':
        validateParameter(currentState, key, Number(value),
            function() {
              return value.match(/^\d+$/);
            });

        return true;

      case 'showCorrectExpectations':
      case 'showWrongExpectations':
      case 'showExpectations':
      case 'showFlaky':
      case 'showLargeExpectations':
      case 'legacyExpectationsSemantics':
      case 'showSkipped':
      case 'showSlow':
      case 'showWontFixSkip':
      case 'expectationsUpdate':
        currentState[key] = value == 'true';

        return true;

      default:
        return false;
    }
  }

  defaultStateValues = {
    sortOrder: BACKWARD,
    sortColumn: 'flakiness',
    showCorrectExpectations: false,
    showWrongExpectations: false,
    showExpectations: false,
    showFlaky: true,
    showLargeExpectations: false,
    legacyExpectationsSemantics: true,
    showWontFixSkip: false,
    showSlow: false,
    showSkipped: false,
    expectationsUpdate: false,
    updateIndex: 0,
    builder: 'Webkit'
  };

  //////////////////////////////////////////////////////////////////////////////
  // GLOBALS
  //////////////////////////////////////////////////////////////////////////////

  // Text to put inside the header for each column of the test table.
  var tableHeaders;
  var perBuilderPlatformAndBuildType = {};
  var perBuilderFailures = {};
  // Map of builder to arrays of tests that are listed in the expectations file
  // but have for that builder.
  var perBuilderWithExpectationsButNoFailures = {};
  // Map of builder to arrays of paths that are skipped. This shows the raw
  // path used in test_expectations.txt rather than the test path since we
  // don't actually have any data here for skipped tests.
  var perBuilderSkippedPaths = {};
  // Maps test path to an array of {builder, testResults} objects.
  var testToResultsMap = {};
  var numFlakyTestsPerBuilder = {};
  // Tests that the user wants to update expectations for.
  var confirmedTests = {};

  function createResultsObjectForTest(test, builder) {
    return {
      test: test,
      builder: builder,
      // HTML for display of the results in the flakiness column
      html: '',
      flips: 0,
      slowestTime: 0,
      slowestNonTimeoutCrashTime: 0,
      meetsExpectations: true,
      isWontFixSkip: false,
      isFlaky: false,
      // Sorted string of missing expectations
      missing: '',
      // String of extra expectations (i.e. expectations that never occur).
      extra: '',
      // HTML for bug IDs for this test for all platforms
      bugsHTML: '',
      // HTML for expectations for this test for all platforms
      expectationsHTML: '',
      // HTML for modifiers for this test for all platforms
      modifiersHTML: '',
      rawResults: '',
      percentFailed: 0
    };
  }

  function getMaxResults() {
    if (currentState.expectationsUpdate ||
        currentState.tests && !currentState.maxResults) {
      return 750;
    }
    return currentState.maxResults || 200;
  }

  function getMatchingElement(stringToMatch, elementsMap) {
    for (var element in elementsMap) {
      if (stringContains(stringToMatch, elementsMap[element]))
        return element;
    }
  }

  function getPlatformAndBuildType(builderName) {
    if (!perBuilderPlatformAndBuildType[builderName]) {
      // If the build name does not contain a platform
      // or build type, assume Windows Release.
      var currentBuildUppercase = builderName.toUpperCase();
      var platform = getMatchingElement(currentBuildUppercase, PLATFORMS) ||
          'WIN';
      var buildType = getMatchingElement(currentBuildUppercase, BUILD_TYPES) ||
          'RELEASE';
      perBuilderPlatformAndBuildType[builderName] = {platform: platform,
          buildType: buildType};
    }
    return perBuilderPlatformAndBuildType[builderName];
  }

  function isDebug(builderName) {
    return getPlatformAndBuildType(builderName).buildType == 'DEBUG';
  }

  /**
   * Returns the expectation string for the given single character result.
   * This string should match the expectations that are put into
   * test_expectations.py.
   *
   * For example, if we start explicitly listing IMAGE result failures,
   * this function should start returning 'IMAGE'.
   */
  function getExpectationsFileStringForResult(result) {
    // For the purposes of comparing against the expecations of a test,
    // consider simplified diff failures as just text failures since
    // the test_expectations file doesn't treat them specially.
    if (result == 'S')
      return 'TEXT';

    if (result == 'N')
      return '';

    return EXPECTATIONS_MAP[result];
  }

  // Map of all tests to true values. This is just so we can have the list of
  // all tests across all the builders.
  var allTests;

  /**
   * Returns a map of all tests to true values. This is just so we can have the
   * list of all tests across all the builders.
   */
  function getAllTests() {
    if (!allTests) {
      allTests = {};
      for (var builder in builders) {
        addTestsForBuilder(builder, allTests);
      }
    }
    return allTests;
  }

  /**
   * Returns an array of tests to be displayed in the individual tests view.
   * Note that a directory can be listed as a test, so we expand that into all
   * tests in the directory.
   */
  function getIndividualTests() {
    if (!currentState.tests) {
      return [];
    } else if (currentState.tests == 'allSlowTests') {
      return getSlowTests();
    } else if (currentState.tests == 'allNeedSlowTests') {
      return getNeedSlowTests();
    } else if (currentState.tests == 'allIncorrectExpectations') {
      return getAllTestsWithIncorrectExpectations();
    }

    var separator = stringContains(currentState.tests, ' ') ? ' ' : ',';
    var testList = currentState.tests.split(separator);

    // Put the tests into an object first and then move them into an array
    // as a way of deduping.
    var tests = {};
    for (var i = 0; i < testList.length; i++) {
      var path = testList[i];

      // Ignore whitespace entries as they'd match every test.
      if (path.match(/^\s*$/))
        continue;

      var allTests = getAllTests();
      var hasAnyMatches = false;
      for (var test in allTests) {
        if (caseInsensitiveContains(test, path)) {
          tests[test] = 1;
          hasAnyMatches = true;
        }
      }

      // If a path doesn't match any tests, then assume it's a full path
      // to a test that passes on all builders.
      if (!hasAnyMatches) {
        tests[path] = 1;
      }
    }

    var testsArray = [];
    for (var test in tests) {
      testsArray.push(test);
    }
    return testsArray;
  }

  function getAllTestsWithIncorrectExpectations() {
    return getAllTestsWithCondition(function(resultsForTest) {
      return !resultsForTest.meetsExpectations;
    });
  }

  function getSlowTests() {
    return getAllTestsWithCondition(isSlowTest);
  }

  function getNeedSlowTests() {
    return getAllTestsWithCondition(function(resultsForTest) {
      if (resultsForTest.isWontFixSkip)
        return false;

      return stringContains(resultsForTest.missing, 'SLOW') ||
          stringContains(resultsForTest.extra, 'SLOW');
    });
  }

  /**
   * Returns whether this test's slowest time is above the cutoff for
   * being a slow test.
   */
  function isSlowTest(resultsForTest) {
    var maxTime = isDebug(resultsForTest.builder) ?
        MIN_SECONDS_FOR_SLOW_TEST_DEBUG :
        MIN_SECONDS_FOR_SLOW_TEST;
    return resultsForTest.slowestNonTimeoutCrashTime > maxTime;
  }

  /**
   * Returns whether this test's slowest time is *well* below the cutoff for
   * being a slow test.
   */
  function isFastTest(resultsForTest) {
    var maxTime = isDebug(resultsForTest.builder) ?
        MIN_SECONDS_FOR_SLOW_TEST_DEBUG :
        MIN_SECONDS_FOR_SLOW_TEST;
    return resultsForTest.slowestNonTimeoutCrashTime < maxTime / 2;
  }

  function getAllTestsWithCondition(conditionFn) {
    processTestRunsForAllBuilders();
    var tests = getAllTests();
    var retVal = [];
    for (var test in tests) {
      var resultsArray = testToResultsMap[test];
      for (var i = 0; i < resultsArray.length; i++) {
        if (conditionFn(resultsArray[i])) {
          retVal.push(test);
          break;
        }
      }
    }
    return retVal;
  }


  /**
   * Adds all the tests for the given builder to the testMapToPopulate.
   */
  function addTestsForBuilder(builder, testMapToPopulate) {
    var tests = resultsByBuilder[builder].tests;
    for (var test in tests) {
      testMapToPopulate[test] = true;
    }
  }

  // Map of all tests to true values by platform and build type.
  // e.g. allTestsByPlatformAndBuildType['WIN']['DEBUG'] will have the union
  // of all tests run on the win-debug builders.
  var allTestsByPlatformAndBuildType = {};
  for (var platform in PLATFORMS) {
    allTestsByPlatformAndBuildType[platform] = {};
  }

  /**
   * Map of all tests to true values by platform and build type.
   * e.g. allTestsByPlatformAndBuildType['WIN']['DEBUG'] will have the union
   * of all tests run on the win-debug builders.
   */
  function getAllTestsWithSamePlatformAndBuildType(platform, buildType) {
    if (!allTestsByPlatformAndBuildType[platform][buildType]) {
      var tests = {};
      for (var thisBuilder in builders) {
        var thisBuilderBuildInfo = getPlatformAndBuildType(thisBuilder);
        if (thisBuilderBuildInfo.buildType == buildType &&
            thisBuilderBuildInfo.platform == platform) {
          addTestsForBuilder(thisBuilder, tests);
        }
      }
      allTestsByPlatformAndBuildType[platform][buildType] = tests;
    }

    return allTestsByPlatformAndBuildType[platform][buildType];
  }

  function addHTMLToIndividualOptionsArray(array, html, isCurrentPlatform) {
    if (html) {
      array.push('<div class="option' +
          (isCurrentPlatform ? '' : ' different-platform') + '">' + html +
          '</div>');
    }
  }

  function addHtmlToOptionsArrays(htmlArrays, expectations, modifiers, bugs,
      isCurrentPlatform) {
    addHTMLToIndividualOptionsArray(htmlArrays.expectations, expectations,
        isCurrentPlatform);
    addHTMLToIndividualOptionsArray(htmlArrays.modifiers, modifiers,
        isCurrentPlatform);
    addHTMLToIndividualOptionsArray(htmlArrays.bugs, getHtmlForBugs(bugs),
        isCurrentPlatform);
  }

  function getExpectations(test, platform, buildType) {
    var testObject = allExpectations[test];
    if (!testObject)
      return null;

    var platformObject;
    while (platform && !(platformObject = testObject[platform])) {
      platform = PLATFORM_FALLBACKS[platform];
    }

    if (platformObject) {
      if (platformObject[buildType])
        return platformObject[buildType]

      if (platformObject[ALL])
        return platformObject[ALL];

      if (testObject[ALL]) {
        var allPlatformObject = testObject[ALL];
        if (allPlatformObject[buildType])
          return allPlatformObject[buildType]

        if (allPlatformObject[ALL])
          return allPlatformObject[ALL]
      }
    }
    return null;
  }

  function populateExpectationsData(resultsObj) {
    var test = resultsObj.test;

    var buildInfo = getPlatformAndBuildType(resultsObj.builder);
    var platform = buildInfo.platform;
    var buildType = buildInfo.buildType;

    var thisPlatformExpectations = getExpectations(test, platform, buildType);

    var htmlArrays = {};
    htmlArrays.expectations = [];
    htmlArrays.modifiers = [];
    htmlArrays.bugs = [];

    var tests = resultsByBuilder[resultsObj.builder].tests;

    var testObject = allExpectations[test];
    var usedExpectations = {};

    for (var platform in allExpectations[test]) {
      var platformObject = testObject[platform];
      for (var buildType in platformObject) {
        var thisExpectations = platformObject[buildType];
        var modifiers = thisExpectations.modifiers;

        // A set of modifiers/expectations can apply to multiple platforms.
        // Only add HTML for each entry once.
        if (usedExpectations[modifiers])
          continue;

        usedExpectations[modifiers] = true;

        var bugs = modifiers.match(/BUG\d+/g);
        if (bugs) {
          for (var j = 0; j < bugs.length; j++) {
            modifiers = modifiers.replace(bugs[j], '');
          }
          modifiers = trimString(modifiers);
          bugs = bugs.join(' ');
        } else {
          bugs = '';
        }

        var expectations = thisExpectations.expectations;
        if (thisExpectations == thisPlatformExpectations) {
          resultsObj.bugs = bugs;
          resultsObj.expectations = expectations;
          resultsObj.modifiers = modifiers;
          resultsObj.isWontFixSkip = stringContains(modifiers, 'WONTFIX') ||
              stringContains(modifiers, 'SKIP');
        } else {
          addHtmlToOptionsArrays(htmlArrays, expectations, modifiers, bugs,
              false);
        }
      }
    }

    if (resultsObj.expectations) {
      addHtmlToOptionsArrays(htmlArrays, resultsObj.expectations,
          resultsObj.modifiers, resultsObj.bugs, true);
    }

    resultsObj.bugsHTML += htmlArrays.bugs.join('<div class=separator></div>');
    resultsObj.expectationsHTML +=
        htmlArrays.expectations.join('<div class=separator></div>');
    resultsObj.modifiersHTML +=
        htmlArrays.modifiers.join('<div class=separator></div>');
  }

  function addFallbacks(addFn, candidates, validValues) {
    var hasAnyValidValues = false;
    for (var i = 0; i < candidates.length; i++) {
      if (candidates[i] in validValues) {
        hasAnyValidValues = true;
        addFn(candidates[i]);
      }
    }
    if (!hasAnyValidValues)
      addFn(ALL);
  }

  function addTestToAllExpectations(test, expectations) {
    for (var j = 0; j < expectations.length; j++) {
      var modifiers = expectations[j].modifiers.split(' ');
      addFallbacks(function(platformKey) {
        addFallbacks(function(buildTypeKey) {
          // Setting the ALL key overrides any previously seen expectations.
          if (platformKey == ALL) {
            for (var i = 0; i < PLATFORMS.length; i++) {
              if (PLATFORMS[i] in allExpectations[test]) {
                // Setting the ALL key overrides any previously seen
                // expectations.
                if (buildTypeKey == ALL)
                  allExpectations[test][PLATFORMS[i]] = {};
              }
            }
          } else if (buildTypeKey == ALL && allExpectations[test]) {
            allExpectations[test][platformKey] = {}
          }

          if (!allExpectations[test])
            allExpectations[test] = {};

          var testHolder = allExpectations[test];
          if (!testHolder[platformKey])
            testHolder[platformKey] = {}

          testHolder[platformKey][buildTypeKey] = expectations[j];
        }, modifiers, BUILD_TYPES);
      }, modifiers, PLATFORMS);
    }
  }

  /**
   * Data structure to hold the processed expectations.
   * allExpectations[testPath][platform][buildType] gets the object that has
   * expectations and modifiers properties for this platform/buildType.
   *
   * platform and buildType both go through fallback sets of keys from most
   * specific key to least specific. For example, on Windows Vista, we first
   * check the platform WIN-VISTA, if there's no such object, we check WIN,
   * then finally we check ALL. For build types, we check the current
   * buildType, then ALL.
   */
  var allExpectations;

  function processExpectations() {
    if (allExpectations)
      return allExpectations;

    allExpectations = {};

    var expectationsArray = [];
    for (var path in expectationsByTest) {
      expectationsArray.push(
          {path: path, expectations: expectationsByTest[path]});
    }

    // Sort the array to hit more specific paths last. More specific
    // paths (e.g. foo/bar/baz.html) override entries for less-specific ones
    // (e.g. foo/bar).
    expectationsArray.sort(getAlphanumericCompare('path'));

    var allTests = getAllTests();
    for (var i = 0; i < expectationsArray.length; i++) {
      var path = expectationsArray[i].path;
      var expectations = expectationsArray[i].expectations;

      var pathMatchesAnyTest = false;
      if (allTests[path]) {
        pathMatchesAnyTest = true;
        addTestToAllExpectations(path, expectations);
      } else {
        for (var test in allTests) {
          if (stringContains(test, path)) {
            pathMatchesAnyTest = true;
            addTestToAllExpectations(test, expectations);
          }
        }
      }

      if (!pathMatchesAnyTest)
        addTestToAllExpectations(path, expectations);
    }
  }

  function processMissingTestsWithExpectations(builder, platform, buildType) {
    var noFailures = [];
    var skipped = [];

    var allTestsForPlatformAndBuildType =
        getAllTestsWithSamePlatformAndBuildType(platform, buildType);
    for (var test in allExpectations) {
      var expectations = getExpectations(test, platform, buildType);

      if (!expectations)
        continue;

      // Test has expectations, but no result in the builders results.
      // This means it's either SKIP or passes on all builds.
      if (!allTestsForPlatformAndBuildType[test] &&
          !stringContains(expectations.modifiers, 'WONTFIX')) {
        if (stringContains(expectations.modifiers, 'SKIP')) {
          skipped.push(test);
        } else if (!expectations.expectations.match(/^\s*PASS\s*$/)) {
          // Don't show tests expected to always pass. This is used in ways like
          // the following:
          // foo/bar = FAIL
          // foo/bar/baz.html = PASS
          noFailures.push({test: test, expectations: expectations.expectations,
              modifiers: expectations.modifiers});
        }
      }
    }

    perBuilderSkippedPaths[builder] = skipped.sort();
    perBuilderWithExpectationsButNoFailures[builder] = noFailures.sort();
  }

  function processTestResultsForBuilderAsync(builder) {
    setTimeout(function() {
        processTestRunsForBuilder(builder);
      }, 0);
  }

  function processTestRunsForAllBuilders() {
    for (var builder in builders)
      processTestRunsForBuilder(builder);
  }

  function processTestRunsForBuilder(builderName) {
    if (perBuilderFailures[builderName]) {
      appendNumFlakyTests(builderName);
      return;
    }

    var start = Date.now();
    numFlakyTestsPerBuilder[builderName] = 0;

    processExpectations();

    var buildInfo = getPlatformAndBuildType(builderName);
    var platform = buildInfo.platform;
    var buildType = buildInfo.buildType;
    processMissingTestsWithExpectations(builderName, platform, buildType);

    var failures = [];
    var allTestsForThisBuilder = resultsByBuilder[builderName].tests;

    for (var test in allTestsForThisBuilder) {
      var resultsForTest = createResultsObjectForTest(test, builderName);
      populateExpectationsData(resultsForTest);

      var rawTest = resultsByBuilder[builderName].tests[test];
      resultsForTest.rawTimes = rawTest.times;
      var rawResults = rawTest.results;
      resultsForTest.rawResults = rawResults;
      resultsForTest.flips = rawResults.length - 1;

      var times = resultsForTest.rawTimes;
      var numTimesSeen = 0;
      var numResultsSeen = 0;
      var resultsIndex = 0;
      var currentResult;
      for (var i = 0;
           i < times.length && numTimesSeen < getMaxResults();
           i++) {
        numTimesSeen += times[i][0];

        while (rawResults[resultsIndex] &&
               numTimesSeen > (numResultsSeen + rawResults[resultsIndex][0])) {
          numResultsSeen += rawResults[resultsIndex][0];
          resultsIndex++;
        }

        if (rawResults && rawResults[resultsIndex])
          currentResult = rawResults[resultsIndex][1];

        var time = times[i][1]

        // Ignore times for crashing/timeout runs for the sake of seeing if
        // a test should be marked slow.
        if (currentResult != 'C' && currentResult != 'T') {
          resultsForTest.slowestNonTimeoutCrashTime =
              Math.max(resultsForTest.slowestNonTimeoutCrashTime, time);
        }
        resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, time);
      }

      processMissingAndExtraExpectations(resultsForTest);

      if (resultsForTest.isFlaky)
        numFlakyTestsPerBuilder[builderName]++;

      failures.push(resultsForTest);

      if (!testToResultsMap[test])
        testToResultsMap[test] = [];
      testToResultsMap[test].push(resultsForTest);
    }

    appendNumFlakyTests(builderName);
    perBuilderFailures[builderName] = failures;
    logTime('processTestRunsForBuilder: ' + builderName, start);
  }

  function processMissingAndExtraExpectations(resultsForTest) {
    // Heuristic for determining whether expectations apply to a given test:
    // -If a test result happens < MIN_RUNS_FOR_FLAKE, then consider it a flaky
    // result and include it in the list of expected results.
    // -Otherwise, grab the first contiguous set of runs with the same result
    // for >= MIN_RUNS_FOR_FLAKE and ignore all following runs >=
    // MIN_RUNS_FOR_FLAKE.
    // This lets us rule out common cases of a test changing expectations for
    // a few runs, then being fixed or otherwise modified in a non-flaky way.
    var rawResults = resultsForTest.rawResults;

    // If the first result is no-data that means the test is skipped or is
    // being run on a different builder (e.g. moved from one shard to another).
    // Ignore these results since we have no real data about what's going on.
    if (rawResults[0][1] == 'N')
      return;

    // Only consider flake if it doesn't happen twice in a row.
    var MIN_RUNS_FOR_FLAKE = 2;
    var resultsMap = {}
    var numResultsSeen = 0;
    var haveSeenNonFlakeResult = false;
    var numRealResults = 0;
    var failedCount = 0;

    var seenResults = {};
    for (var i = 0;
         i < rawResults.length && numResultsSeen < getMaxResults();
         i++) {
      var numResults = rawResults[i][0];
      numResultsSeen += numResults;

      var result = rawResults[i][1];
      if (isFailingResult(result)) {
        failedCount += numResults;
      }

      var hasMinRuns = numResults >= MIN_RUNS_FOR_FLAKE;
      if (haveSeenNonFlakeResult && hasMinRuns) {
        continue;
      } else if (hasMinRuns) {
        haveSeenNonFlakeResult = true;
      } else if (!seenResults[result]) {
        // Only consider a short-lived result if we've seen it more than once.
        // Otherwise, we include lots of false-positives due to tests that fail
        // for a couple runs and then start passing.
        seenResults[result] = true;
        continue;
      }

      var expectation = getExpectationsFileStringForResult(result);
      resultsMap[expectation] = true;
      numRealResults++;
    }

    resultsForTest.isFlaky = numRealResults > 1;
    // Calculate the % of times the test failed - how flaky is it?
    resultsForTest.percentFailed =
        Math.round(failedCount / numResultsSeen * 100);

    var expectationsArray = resultsForTest.expectations ?
        resultsForTest.expectations.split(' ') : [];
    var extraExpectations = expectationsArray.filter(
        function(element) {
          // TODO(ojan): Once all the FAIL lines are removed from
          // test_expectations.txt, delete all the legacyExpectationsSemantics
          // code.
          if (currentState.legacyExpectationsSemantics) {
            if (element == 'FAIL') {
              for (var i = 0; i < FAIL_RESULTS.length; i++) {
                if (resultsMap[FAIL_RESULTS[i]])
                  return false;
              }
              return true;
            }
          }

          return element && !resultsMap[element] &&
              !stringContains(element, 'BUG');
        });

    var missingExpectations = [];
    for (var result in resultsMap) {
      var hasExpectation = false;
      for (var i = 0; i < expectationsArray.length; i++) {
        var expectation = expectationsArray[i];
        // TODO(ojan): Once all the FAIL lines are removed from
        // test_expectations.txt, delete all the legacyExpectationsSemantics
        // code.
        if (currentState.legacyExpectationsSemantics) {
          if (expectation == 'FAIL') {
            for (var j = 0; j < FAIL_RESULTS.length; j++) {
              if (result == FAIL_RESULTS[j]) {
                hasExpectation = true;
                break;
              }
            }
          }
        }

        if (result == expectation) {
          hasExpectation = true;
        }

        if (hasExpectation)
          break;
      }
      // If we have no expectations for a test and it only passes, then don't
      // list PASS as a missing expectation. We only want to list PASS if it
      // flaky passes, so there would be other expectations.
      if (!hasExpectation &&
          !(!expectationsArray.length && result == 'PASS' &&
            numRealResults == 1))
        missingExpectations.push(result);
    }

    // Only highlight tests that take > 2 seconds as needing to be marked as
    // slow. There are too many tests that take ~2 seconds every couple
    // hundred runs. It's not worth the manual maintenance effort.
    // Also, if a test times out, then it should not be marked as slow.
    var minTimeForNeedsSlow = isDebug(resultsForTest.builder) ? 2 : 1;
    if (isSlowTest(resultsForTest) && !resultsMap['TIMEOUT'] &&
        (!resultsForTest.modifiers ||
         !stringContains(resultsForTest.modifiers, 'SLOW'))) {
      missingExpectations.push('SLOW');
    } else if (isFastTest(resultsForTest) &&
               resultsForTest.modifiers &&
               stringContains(resultsForTest.modifiers, 'SLOW')) {
      extraExpectations.push('SLOW');
    }

    resultsForTest.meetsExpectations =
        !missingExpectations.length && !extraExpectations.length;
    resultsForTest.missing = missingExpectations.sort().join(' ');
    resultsForTest.extra = extraExpectations.sort().join(' ');
  }

  var bugUrlPrefix = '<a href="http://';
  var bugUrlPostfix = '/$1">$1</a> ';
  var internalBugReplaceValue = bugUrlPrefix + 'b' + bugUrlPostfix;
  var externalBugReplaceValue = bugUrlPrefix + 'crbug.com' + bugUrlPostfix;

  /**
   * Returns the BUG modifiers linking to the bug.
   * Bugs with 4 or 5 digits are crbug.com bugs. Bugs with 6 or 7 digits
   * are internal google bugs.
   */
  function getHtmlForBugs(bugs) {
    bugs = bugs.replace(/BUG(\d{4})(\ |$)/g, externalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{5})(\ |$)/g, externalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{6})(\ |$)/g, internalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{7})(\ |$)/g, internalBugReplaceValue);
    return bugs;
  }

  function getLinkHTMLToOpenWindow(url, text) {
    return '<div class=link onclick="window.open(\'' + url + '\')">' + text +
        '</div>';
  }

  function createBlameListHTML(revisions, index, urlBase, separator, repo) {
    var thisRevision = revisions[index];
    if (!thisRevision)
      return '';

    var previousRevision = revisions[index + 1];
    if (previousRevision && previousRevision != thisRevision) {
      previousRevision++;
      return getLinkHTMLToOpenWindow(
          urlBase + thisRevision + separator + previousRevision,
          repo + ' blamelist r' + previousRevision + ':r' + thisRevision);
    } else {
      return 'At ' + repo + ' revision: ' + thisRevision;
    }
  }

  function showPopupForBuild(e, builder, index) {
    var html = '';

    var time = resultsByBuilder[builder].secondsSinceEpoch[index];
    if (time) {
      var date = new Date(time * 1000);
      html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
    }

    html += '<ul><li>' +
      createBlameListHTML(resultsByBuilder[builder].webkitRevision, index,
          'http://trac.webkit.org/log/?rev=', '&stop_rev=', 'WebKit') +
      '</li><li>' +
      createBlameListHTML(resultsByBuilder[builder].chromeRevision, index,
          'http://build.chromium.org/buildbot/perf/dashboard/ui/' +
          'changelog.html?url=/trunk/src&mode=html&range=', ':', 'Chrome') +
      '</li>';

    var chromeRevision = resultsByBuilder[builder].chromeRevision[index];
    if (chromeRevision) {
      html += '<li><a href="' + TEST_RESULTS_BASE_PATH + builders[builder] +
          '/' + chromeRevision + '/layout-test-results.zip' +
          '">layout-test-results.zip</a></li>';
    }

    var buildNumbers = resultsByBuilder[builder].buildNumbers;
    html += '<li>' +
        getLinkHTMLToOpenWindow(BUILDERS_BASE_PATH + builder + '/builds/' +
        buildNumbers[index], 'Build log') + '</li></ul>';

    showPopup(e, html);
  }

  function showPopupForTest(e, test) {
    showPopup(e, getHTMLForIndividulTestOnAllBuilders(test));
    appendExpectations();
  }

  function getHtmlForTestResults(test) {
    var html = '';
    var results = test.rawResults.concat();
    var times = test.rawTimes.concat();
    var builder = test.builder;
    var buildNumbers = resultsByBuilder[builder].buildNumbers;

    var indexToReplaceCurrentResult = -1;
    var indexToReplaceCurrentTime = -1;
    var currentResultArray, currentTimeArray, currentResult, innerHTML,
        resultString;
    var maxIndex = Math.min(buildNumbers.length, getMaxResults());
    for (var i = 0; i < maxIndex; i++) {
      if (i > indexToReplaceCurrentResult) {
        currentResultArray = results.shift();
        if (currentResultArray) {
          currentResult = currentResultArray[1];
          // Treat simplified diff failures as just text failures.
          if (currentResult == 'S')
            currentResult = 'F';
          indexToReplaceCurrentResult += currentResultArray[0];
        } else {
          currentResult = 'N';
          indexToReplaceCurrentResult += buildNumbers.length;
        }

        resultString = getExpectationsFileStringForResult(currentResult);
      }

      if (i > indexToReplaceCurrentTime) {
        currentTimeArray = times.shift();
        var currentTime = 0;
        if (currentResultArray) {
          currentTime = currentTimeArray[1];
          indexToReplaceCurrentTime += currentTimeArray[0];
        } else {
          indexToReplaceCurrentTime += buildNumbers.length;
        }
        innerHTML = currentTime || '&nbsp;';
      }

      html += '<td title="' + resultString + '. Click for more info." ' +
          'class="results ' + currentResult +
          '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i +
          ')\'>' + innerHTML + '</td>';

      var webkitRevision = resultsByBuilder[builder].webkitRevision;
      var isWebkitMerge = webkitRevision[i + 1] &&
          webkitRevision[i] != webkitRevision[i + 1];
      if (isWebkitMerge)
        html += '<td class=merge></td>';
    }
    return html;
  }

  function getHTMLForTestsWithExpectationsButNoFailures(builder) {
    var tests = perBuilderWithExpectationsButNoFailures[builder];
    var skippedPaths = perBuilderSkippedPaths[builder];

    var html = '';
    if (tests.length || skippedPaths.length) {
      var buildInfo = getPlatformAndBuildType(builder);
      html += '<h2>Expectations for ' + buildInfo.platform + '-' +
          buildInfo.buildType + ':</h2>';
    }

    var open = '<div onclick="selectContents(this)">';

    if (tests.length) {
      html +=  '<h3>Have not failed in last ' +
          resultsByBuilder[builder].buildNumbers.length +
          ' runs.</h3><div id="passing-tests">';

      for (var i = 0; i < tests.length; i++) {
        html += open + tests[i].test + '</div>';
      }

      html += '</div>';
    }

    if (skippedPaths.length) {
      html += '<h3>' +
          getLinkHTMLToToggleState('showSkipped',
              'Skipped tests in test_expectations.txt') +
          '</h3>';

      if (currentState.showSkipped) {
        html += '<div id="passing-tests">' + open +
          skippedPaths.join('</div>' + open) + '</div></div>';
      }
    }
    return html;
  }

  /**
   * Returns whether the test has passed continuously for at
   * least as many runs as we are showing results for.
   */
   function hasPassedInMaxRuns(testResult) {
     var results = testResult.rawResults;
     return results[0][1] == 'P' && results[0][0] >= getMaxResults();
   }

  /**
   * Returns whether we should exclude test results from the test table.
   */
  function shouldHideTest(testResult) {
    if (testResult.isWontFixSkip)
      return !currentState.showWontFixSkip;

    if (testResult.isFlaky)
      return !currentState.showFlaky;

    if (isSlowTest(testResult))
      return !currentState.showSlow;

    if (testResult.meetsExpectations)
      return !currentState.showCorrectExpectations;

    return !currentState.showWrongExpectations;
  }

  /**
   * Sets the browser's selection to the element's contents.
   */
  function selectContents(element) {
    window.getSelection().selectAllChildren(element);
  }

  function getHTMLForSingleTestRow(test, opt_isCrossBuilderView) {
    if (!opt_isCrossBuilderView && shouldHideTest(test)) {
      // The innerHTML call is considerably faster if we exclude the rows for
      // items we're not showing than if we hide them using display:none.
      // For the crossBuilderView, we want to show all rows the user is
      // explicitly listing tests to view.
      return '';
    }

    // If opt_isCrossBuilderView is true, we're just viewing a single test
    // with results for many builders, so the first column is builder names
    // instead of test paths.
    var testCellHTML = opt_isCrossBuilderView ? test.builder :
      '<span class="link" onclick="showPopupForTest(event, \'' + test.test +
      '\');selectContents(this);return false;">' + test.test + '</span>';

    return '<tr class="' +
      (test.meetsExpectations ? '' : 'wrong-expectations') +
      // TODO(ojan): If a test is a chrome/ or a pending/ test, point to
      // src.chromium.org instead of trac.webkit.org.
      '"><td class=test-link>' + testCellHTML +
      '</td><td class=options-container>' + test.bugsHTML +
      '</td><td class=options-container>' + test.modifiersHTML +
      '</td><td class=options-container>' + test.expectationsHTML +
      '</td><td>' + test.missing +
      '</td><td>' + test.extra +
      '</td><td>' + (test.slowestTime ? test.slowestTime + 's' : '') +
      '</td><td>' + test.percentFailed +
      '</td>' + getHtmlForTestResults(test) + '</tr>';
  }

  function getSortColumnFromTableHeader(headerText) {
    return headerText.split(' ', 1)[0];
  }

  function getHTMLForTestTable(rowsHTML) {
    var html = '<table class=test-table><thead><tr>';
    for (var i = 0; i < tableHeaders.length; i++) {<commit_msg>Add "File Bug" link to flakiness dashboard to ease filing new layout test regression bugs.<commit_after><!DOCTYPE HTML>
<html>

<head>
  <title>Webkit Layout Test History</title>
  <style>
    body {
      font-family: arial;
      font-size: 13px;
    }
    h2 {
      font-size: 16px;
      margin-bottom: .25em;
    }
    h3 {
      font-size: 13px;
      margin: 0;
    }
    #max-results-form {
      display: inline;
    }
    #max-results-input {
      width: 30px;
    }
    #tests-form {
      display: -webkit-box;
    }
    #tests-form > * {
      display: -webkit-box;
    }
    #tests-form > div {
      -webkit-box-flex: 0;
    }
    #tests-input {
      -webkit-box-flex: 1;
    }
    .test-link {
      white-space: normal;
    }
    .test-link, .options-container {
      padding: 0 2px;
    }
    .test-table {
      white-space: nowrap;
    }
    .test-table {
      width: 100%;
    }
    .test-table tr {
      border: 1px solid red;
      background-color: #E8E8E8;
    }
    .test-table tbody tr:hover {
      opacity: .7;
    }
    .test-table th {
      -webkit-user-select: none;
      -moz-user-select: none;
    }
    .link, .sortable .header-text {
      color: blue;
      text-decoration: underline;
      cursor: pointer;
    }
    .table-header-content,
    .table-header-content * {
      display: -webkit-box;
    }
    .table-header-content * {
      -webkit-box-flex: 1;
      cursor: pointer;
      white-space: normal;
    }
    .results {
      cursor: pointer;
      padding: 0;
      font-size: 10px;
      text-align: center;
    }
    #legend {
      position: fixed;
      top: 5px;
      right: 5px;
      width: 400px;
      padding: 2px;
      border: 2px solid grey;
      background-color: white;
      z-index: 1;
    }
    #legend ul, #legend ol {
      margin-top: 0;
      margin-bottom: 5px;
    }
    #legend-contents * {
      margin: 3px 0;
      padding: 0 2px;
      float: left;
      border: 1px solid grey;
    }
    #builders * {
      margin: 0 5px;
      display: inline-block;
      white-space: nowrap;
    }
    .test-table .wrong-expectations,
    .wrong-expectations {
      background-color: #pink;
    }
    .P {
      background-color: #8fdf5f;
    }
    .N {
      background-color: #fff;
    }
    .C {
      background-color: #ffc343;
    }
    .T {
      background-color: #fffc6c;
    }
    .I {
      background-color: #69f;
    }
    .S {
      background-color: #c6c;
    }
    .F {
      background-color: #e98080;
    }
    .O {
      background-color: #8a7700;
    }
    .Z {
      background-color: #96f;
    }
    .merge {
      background-color: black;
      color: white;
    }
    table .merge {
      width: 1px;
    }
    .separator {
      border: 1px solid lightgray;
      height: 0px;
    }
    .different-platform {
      color: gray;
      font-size: 10px;
    }
    .current-builder {
      font-weight: bold;
    }
    #passing-tests {
      -webkit-column-count: 3;
      -webkit-column-gap: 25px;
      -webkit-column-rule: 1px dashed black;
      -moz-column-count: 3;
      -moz-column-gap: 25px;
      -moz-column-rule: 1px dashed black;
    }
    .not-found {
      color: red;
      font-size: large;
    }
    #loading-ui {
      position: fixed;
      top: 0;
      left: 0;
      background-color: yellow;
      padding: 5px;
      text-align: center;
      font-weight: bold;
    }
    #popup {
      background-color: white;
      z-index: 1;
      position: absolute;
      border: 3px solid grey;
      padding: 3px;
      -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
      -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
      -webkit-border-radius: 5px;
      -moz-border-radius: 5px;
    }
    #popup > ul {
      margin: 0;
      padding-left: 20px;
    }
    .expectations-container {
      clear: both;
    }
    .expectations-item {
      float: left;
      border: 1px solid grey;
    }
    .expectations-item .expectation {
      width: 400px;
      height: 300px;
      border: 0;
      border-top: 1px solid grey;
    }
    .expectations-item .large {
      width: 800px;
      height: 600px;
    }
    .expectations-item .checksum {
      height: 30px;
    }
    .used-platform {
      float: right;
      color: darkblue;
      margin: 0 5px;
    }
    .expectations-title {
      /* Hack to make a containing block for absolute positioned elements. */
      position: relative;
      overflow: hidden;
      clear: both;
    }
    .title {
      /* Position absolutely so the container does not grow to contain this. */
      position: absolute;
    }
    .platforms {
      position: absolute;
      background-color: white;
      right: 0;
      z-index: 1;
    }
    .file-bug {
      font-weight: bold;
      font-size: 11px;
    }
  </style>

  <script src="dashboards/dashboard_base.js"></script>
  <script>
  /**
   * @fileoverview Creates a dashboard for multiple runs of a given set of tests
   * on the buildbots. Pulls in JSONP-ish files with the results for running
   * tests on a given builder (i.e. ADD_RESULTS(json_here)) and the expectations
   * for all tests on all builders (i.e. ADD_EXPECTATIONS(json_here)).
   *
   * This shows flakiness of the tests as well as runtimes for slow tests.
   *
   * Also, each column in the dashboard is sortable.
   *
   * Currently, only webkit tests are supported, but adding other test types
   * should just require the following steps:
   *   -generate results.json and expectations.json for these tests
   *   -copy them to the appropriate location
   *   -add the builder name to the list of builders below.
   */

  //////////////////////////////////////////////////////////////////////////////
  // CONSTANTS
  //////////////////////////////////////////////////////////////////////////////
  var ALL = 'ALL';
  var FORWARD = 'forward';
  var BACKWARD = 'backward';
  var CHROME_TEST_BASE_URL = 'http://src.chromium.org/viewvc/chrome/trunk/' +
      'src/webkit/data/layout_tests/platform/';
  var TEST_URL_BASE_PATH =
      'http://trac.webkit.org/projects/webkit/browser/trunk/LayoutTests/';
  var BUILDERS_BASE_PATH =
      'http://build.chromium.org/buildbot/waterfall/builders/';
  var TEST_RESULTS_BASE_PATH =
      'http://build.chromium.org/buildbot/layout_test_results/';
  var PLATFORMS = {
    'MAC': 'MAC',
    'LINUX': 'LINUX',
    'WIN': 'WIN',
    'WIN-XP': 'WIN-XP',
    'WIN-VISTA': 'WIN-VISTA'
  };
  var PLATFORM_FALLBACKS = {
    'WIN': 'ALL',
    'WIN-XP': 'WIN',
    'WIN-VISTA': 'WIN',
    'LINUX': 'ALL',
    'MAC': 'ALL'
  };
  var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'};
  var BASE_TABLE_HEADERS = ['bugs', 'modifiers', 'expectations', 'missing',
      'extra', 'slowest run', '% fail', 'flakiness (numbers are runtimes in seconds)'];
  var MIN_SECONDS_FOR_SLOW_TEST = 4;
  var MIN_SECONDS_FOR_SLOW_TEST_DEBUG = 2 * MIN_SECONDS_FOR_SLOW_TEST;
  var FAIL_RESULTS = ['IMAGE', 'IMAGE+TEXT', 'TEXT', 'SIMPLIFIED', 'MISSING'];
  var CHUNK_SIZE = 25;

  //////////////////////////////////////////////////////////////////////////////
  // Methods and objects from dashboard_base.js to override.
  //////////////////////////////////////////////////////////////////////////////
  function generatePage() {
    if (currentState.useTestData)
      return;

    document.body.innerHTML = '<div id="loading-ui">LOADING...</div>';

    if (currentState.tests) {
      generatePageForIndividualTests(getIndividualTests());
    } else if (currentState.expectationsUpdate) {
      generatePageForExpectationsUpdate();
    } else {
      createTableHeadersArray('test');
      generatePageForBuilder(currentState.builder);
    }

    var maxResultsInput = $('max-results-input');
    if (maxResultsInput)
      maxResultsInput.value = getMaxResults();

    for (var builder in builders) {
      processTestResultsForBuilderAsync(builder);
    }
  }

  function handleValidHashParameter(key, value) {
    switch(key) {
      case 'tests':
        validateParameter(currentState, key, value,
            function() {
              return isValidName(value);
            });

        return true;

      case 'builder':
        validateParameter(currentState, key, value,
            function() {
              return value in builders;
            });

        return true;

      case 'sortColumn':
        validateParameter(currentState, key, value,
            function() {
              for (var i = 0; i < BASE_TABLE_HEADERS.length; i++) {
                if (value ==
                        getSortColumnFromTableHeader(BASE_TABLE_HEADERS[i]))
                  return true;
              }
              return value == 'test' || value == 'builder';
            });

        return true;

      case 'sortOrder':
        validateParameter(currentState, key, value,
            function() {
              return value == FORWARD || value == BACKWARD;
            });

        return true;

      case 'maxResults':
      case 'updateIndex':
        validateParameter(currentState, key, Number(value),
            function() {
              return value.match(/^\d+$/);
            });

        return true;

      case 'showCorrectExpectations':
      case 'showWrongExpectations':
      case 'showExpectations':
      case 'showFlaky':
      case 'showLargeExpectations':
      case 'legacyExpectationsSemantics':
      case 'showSkipped':
      case 'showSlow':
      case 'showWontFixSkip':
      case 'expectationsUpdate':
        currentState[key] = value == 'true';

        return true;

      default:
        return false;
    }
  }

  defaultStateValues = {
    sortOrder: BACKWARD,
    sortColumn: 'flakiness',
    showCorrectExpectations: false,
    showWrongExpectations: false,
    showExpectations: false,
    showFlaky: true,
    showLargeExpectations: false,
    legacyExpectationsSemantics: true,
    showWontFixSkip: false,
    showSlow: false,
    showSkipped: false,
    expectationsUpdate: false,
    updateIndex: 0,
    builder: 'Webkit'
  };

  //////////////////////////////////////////////////////////////////////////////
  // GLOBALS
  //////////////////////////////////////////////////////////////////////////////

  // Text to put inside the header for each column of the test table.
  var tableHeaders;
  var perBuilderPlatformAndBuildType = {};
  var perBuilderFailures = {};
  // Map of builder to arrays of tests that are listed in the expectations file
  // but have for that builder.
  var perBuilderWithExpectationsButNoFailures = {};
  // Map of builder to arrays of paths that are skipped. This shows the raw
  // path used in test_expectations.txt rather than the test path since we
  // don't actually have any data here for skipped tests.
  var perBuilderSkippedPaths = {};
  // Maps test path to an array of {builder, testResults} objects.
  var testToResultsMap = {};
  var numFlakyTestsPerBuilder = {};
  // Tests that the user wants to update expectations for.
  var confirmedTests = {};

  function createResultsObjectForTest(test, builder) {
    return {
      test: test,
      builder: builder,
      // HTML for display of the results in the flakiness column
      html: '',
      flips: 0,
      slowestTime: 0,
      slowestNonTimeoutCrashTime: 0,
      meetsExpectations: true,
      isWontFixSkip: false,
      isFlaky: false,
      // Sorted string of missing expectations
      missing: '',
      // String of extra expectations (i.e. expectations that never occur).
      extra: '',
      // HTML for bug IDs for this test for all platforms
      bugsHTML: '',
      // HTML for expectations for this test for all platforms
      expectationsHTML: '',
      // HTML for modifiers for this test for all platforms
      modifiersHTML: '',
      rawResults: '',
      percentFailed: 0
    };
  }

  function getMaxResults() {
    if (currentState.expectationsUpdate ||
        currentState.tests && !currentState.maxResults) {
      return 750;
    }
    return currentState.maxResults || 200;
  }

  function getMatchingElement(stringToMatch, elementsMap) {
    for (var element in elementsMap) {
      if (stringContains(stringToMatch, elementsMap[element]))
        return element;
    }
  }

  function getPlatformAndBuildType(builderName) {
    if (!perBuilderPlatformAndBuildType[builderName]) {
      // If the build name does not contain a platform
      // or build type, assume Windows Release.
      var currentBuildUppercase = builderName.toUpperCase();
      var platform = getMatchingElement(currentBuildUppercase, PLATFORMS) ||
          'WIN';
      var buildType = getMatchingElement(currentBuildUppercase, BUILD_TYPES) ||
          'RELEASE';
      perBuilderPlatformAndBuildType[builderName] = {platform: platform,
          buildType: buildType};
    }
    return perBuilderPlatformAndBuildType[builderName];
  }

  function isDebug(builderName) {
    return getPlatformAndBuildType(builderName).buildType == 'DEBUG';
  }

  /**
   * Returns the expectation string for the given single character result.
   * This string should match the expectations that are put into
   * test_expectations.py.
   *
   * For example, if we start explicitly listing IMAGE result failures,
   * this function should start returning 'IMAGE'.
   */
  function getExpectationsFileStringForResult(result) {
    // For the purposes of comparing against the expecations of a test,
    // consider simplified diff failures as just text failures since
    // the test_expectations file doesn't treat them specially.
    if (result == 'S')
      return 'TEXT';

    if (result == 'N')
      return '';

    return EXPECTATIONS_MAP[result];
  }

  // Map of all tests to true values. This is just so we can have the list of
  // all tests across all the builders.
  var allTests;

  /**
   * Returns a map of all tests to true values. This is just so we can have the
   * list of all tests across all the builders.
   */
  function getAllTests() {
    if (!allTests) {
      allTests = {};
      for (var builder in builders) {
        addTestsForBuilder(builder, allTests);
      }
    }
    return allTests;
  }

  /**
   * Returns an array of tests to be displayed in the individual tests view.
   * Note that a directory can be listed as a test, so we expand that into all
   * tests in the directory.
   */
  function getIndividualTests() {
    if (!currentState.tests) {
      return [];
    } else if (currentState.tests == 'allSlowTests') {
      return getSlowTests();
    } else if (currentState.tests == 'allNeedSlowTests') {
      return getNeedSlowTests();
    } else if (currentState.tests == 'allIncorrectExpectations') {
      return getAllTestsWithIncorrectExpectations();
    }

    var separator = stringContains(currentState.tests, ' ') ? ' ' : ',';
    var testList = currentState.tests.split(separator);

    // Put the tests into an object first and then move them into an array
    // as a way of deduping.
    var tests = {};
    for (var i = 0; i < testList.length; i++) {
      var path = testList[i];

      // Ignore whitespace entries as they'd match every test.
      if (path.match(/^\s*$/))
        continue;

      var allTests = getAllTests();
      var hasAnyMatches = false;
      for (var test in allTests) {
        if (caseInsensitiveContains(test, path)) {
          tests[test] = 1;
          hasAnyMatches = true;
        }
      }

      // If a path doesn't match any tests, then assume it's a full path
      // to a test that passes on all builders.
      if (!hasAnyMatches) {
        tests[path] = 1;
      }
    }

    var testsArray = [];
    for (var test in tests) {
      testsArray.push(test);
    }
    return testsArray;
  }

  function getAllTestsWithIncorrectExpectations() {
    return getAllTestsWithCondition(function(resultsForTest) {
      return !resultsForTest.meetsExpectations;
    });
  }

  function getSlowTests() {
    return getAllTestsWithCondition(isSlowTest);
  }

  function getNeedSlowTests() {
    return getAllTestsWithCondition(function(resultsForTest) {
      if (resultsForTest.isWontFixSkip)
        return false;

      return stringContains(resultsForTest.missing, 'SLOW') ||
          stringContains(resultsForTest.extra, 'SLOW');
    });
  }

  /**
   * Returns whether this test's slowest time is above the cutoff for
   * being a slow test.
   */
  function isSlowTest(resultsForTest) {
    var maxTime = isDebug(resultsForTest.builder) ?
        MIN_SECONDS_FOR_SLOW_TEST_DEBUG :
        MIN_SECONDS_FOR_SLOW_TEST;
    return resultsForTest.slowestNonTimeoutCrashTime > maxTime;
  }

  /**
   * Returns whether this test's slowest time is *well* below the cutoff for
   * being a slow test.
   */
  function isFastTest(resultsForTest) {
    var maxTime = isDebug(resultsForTest.builder) ?
        MIN_SECONDS_FOR_SLOW_TEST_DEBUG :
        MIN_SECONDS_FOR_SLOW_TEST;
    return resultsForTest.slowestNonTimeoutCrashTime < maxTime / 2;
  }

  function getAllTestsWithCondition(conditionFn) {
    processTestRunsForAllBuilders();
    var tests = getAllTests();
    var retVal = [];
    for (var test in tests) {
      var resultsArray = testToResultsMap[test];
      for (var i = 0; i < resultsArray.length; i++) {
        if (conditionFn(resultsArray[i])) {
          retVal.push(test);
          break;
        }
      }
    }
    return retVal;
  }


  /**
   * Adds all the tests for the given builder to the testMapToPopulate.
   */
  function addTestsForBuilder(builder, testMapToPopulate) {
    var tests = resultsByBuilder[builder].tests;
    for (var test in tests) {
      testMapToPopulate[test] = true;
    }
  }

  // Map of all tests to true values by platform and build type.
  // e.g. allTestsByPlatformAndBuildType['WIN']['DEBUG'] will have the union
  // of all tests run on the win-debug builders.
  var allTestsByPlatformAndBuildType = {};
  for (var platform in PLATFORMS) {
    allTestsByPlatformAndBuildType[platform] = {};
  }

  /**
   * Map of all tests to true values by platform and build type.
   * e.g. allTestsByPlatformAndBuildType['WIN']['DEBUG'] will have the union
   * of all tests run on the win-debug builders.
   */
  function getAllTestsWithSamePlatformAndBuildType(platform, buildType) {
    if (!allTestsByPlatformAndBuildType[platform][buildType]) {
      var tests = {};
      for (var thisBuilder in builders) {
        var thisBuilderBuildInfo = getPlatformAndBuildType(thisBuilder);
        if (thisBuilderBuildInfo.buildType == buildType &&
            thisBuilderBuildInfo.platform == platform) {
          addTestsForBuilder(thisBuilder, tests);
        }
      }
      allTestsByPlatformAndBuildType[platform][buildType] = tests;
    }

    return allTestsByPlatformAndBuildType[platform][buildType];
  }

  function addHTMLToIndividualOptionsArray(array, html, isCurrentPlatform) {
    if (html) {
      array.push('<div class="option' +
          (isCurrentPlatform ? '' : ' different-platform') + '">' + html +
          '</div>');
    }
  }

  function addHtmlToOptionsArrays(htmlArrays, expectations, modifiers, bugs,
      isCurrentPlatform) {
    addHTMLToIndividualOptionsArray(htmlArrays.expectations, expectations,
        isCurrentPlatform);
    addHTMLToIndividualOptionsArray(htmlArrays.modifiers, modifiers,
        isCurrentPlatform);
    addHTMLToIndividualOptionsArray(htmlArrays.bugs, getHtmlForBugs(bugs),
        isCurrentPlatform);
  }

  function getExpectations(test, platform, buildType) {
    var testObject = allExpectations[test];
    if (!testObject)
      return null;

    var platformObject;
    while (platform && !(platformObject = testObject[platform])) {
      platform = PLATFORM_FALLBACKS[platform];
    }

    if (platformObject) {
      if (platformObject[buildType])
        return platformObject[buildType]

      if (platformObject[ALL])
        return platformObject[ALL];

      if (testObject[ALL]) {
        var allPlatformObject = testObject[ALL];
        if (allPlatformObject[buildType])
          return allPlatformObject[buildType]

        if (allPlatformObject[ALL])
          return allPlatformObject[ALL]
      }
    }
    return null;
  }

  function populateExpectationsData(resultsObj) {
    var test = resultsObj.test;

    var buildInfo = getPlatformAndBuildType(resultsObj.builder);
    var platform = buildInfo.platform;
    var buildType = buildInfo.buildType;

    var thisPlatformExpectations = getExpectations(test, platform, buildType);

    var htmlArrays = {};
    htmlArrays.expectations = [];
    htmlArrays.modifiers = [];
    htmlArrays.bugs = [];

    var tests = resultsByBuilder[resultsObj.builder].tests;

    var testObject = allExpectations[test];
    var usedExpectations = {};

    for (var platform in allExpectations[test]) {
      var platformObject = testObject[platform];
      for (var buildType in platformObject) {
        var thisExpectations = platformObject[buildType];
        var modifiers = thisExpectations.modifiers;

        // A set of modifiers/expectations can apply to multiple platforms.
        // Only add HTML for each entry once.
        if (usedExpectations[modifiers])
          continue;

        usedExpectations[modifiers] = true;

        var bugs = modifiers.match(/BUG\d+/g);
        if (bugs) {
          for (var j = 0; j < bugs.length; j++) {
            modifiers = modifiers.replace(bugs[j], '');
          }
          modifiers = trimString(modifiers);
          bugs = bugs.join(' ');
        } else {
          bugs = '';
        }

        var expectations = thisExpectations.expectations;
        if (thisExpectations == thisPlatformExpectations) {
          resultsObj.bugs = bugs;
          resultsObj.expectations = expectations;
          resultsObj.modifiers = modifiers;
          resultsObj.isWontFixSkip = stringContains(modifiers, 'WONTFIX') ||
              stringContains(modifiers, 'SKIP');
        } else {
          addHtmlToOptionsArrays(htmlArrays, expectations, modifiers, bugs,
              false);
        }
      }
    }

    if (resultsObj.expectations) {
      addHtmlToOptionsArrays(htmlArrays, resultsObj.expectations,
          resultsObj.modifiers, resultsObj.bugs, true);
    }

    resultsObj.bugsHTML += htmlArrays.bugs.join('<div class=separator></div>');
    resultsObj.expectationsHTML +=
        htmlArrays.expectations.join('<div class=separator></div>');
    resultsObj.modifiersHTML +=
        htmlArrays.modifiers.join('<div class=separator></div>');
  }

  function addFallbacks(addFn, candidates, validValues) {
    var hasAnyValidValues = false;
    for (var i = 0; i < candidates.length; i++) {
      if (candidates[i] in validValues) {
        hasAnyValidValues = true;
        addFn(candidates[i]);
      }
    }
    if (!hasAnyValidValues)
      addFn(ALL);
  }

  function addTestToAllExpectations(test, expectations) {
    for (var j = 0; j < expectations.length; j++) {
      var modifiers = expectations[j].modifiers.split(' ');
      addFallbacks(function(platformKey) {
        addFallbacks(function(buildTypeKey) {
          // Setting the ALL key overrides any previously seen expectations.
          if (platformKey == ALL) {
            for (var i = 0; i < PLATFORMS.length; i++) {
              if (PLATFORMS[i] in allExpectations[test]) {
                // Setting the ALL key overrides any previously seen
                // expectations.
                if (buildTypeKey == ALL)
                  allExpectations[test][PLATFORMS[i]] = {};
              }
            }
          } else if (buildTypeKey == ALL && allExpectations[test]) {
            allExpectations[test][platformKey] = {}
          }

          if (!allExpectations[test])
            allExpectations[test] = {};

          var testHolder = allExpectations[test];
          if (!testHolder[platformKey])
            testHolder[platformKey] = {}

          testHolder[platformKey][buildTypeKey] = expectations[j];
        }, modifiers, BUILD_TYPES);
      }, modifiers, PLATFORMS);
    }
  }

  /**
   * Data structure to hold the processed expectations.
   * allExpectations[testPath][platform][buildType] gets the object that has
   * expectations and modifiers properties for this platform/buildType.
   *
   * platform and buildType both go through fallback sets of keys from most
   * specific key to least specific. For example, on Windows Vista, we first
   * check the platform WIN-VISTA, if there's no such object, we check WIN,
   * then finally we check ALL. For build types, we check the current
   * buildType, then ALL.
   */
  var allExpectations;

  function processExpectations() {
    if (allExpectations)
      return allExpectations;

    allExpectations = {};

    var expectationsArray = [];
    for (var path in expectationsByTest) {
      expectationsArray.push(
          {path: path, expectations: expectationsByTest[path]});
    }

    // Sort the array to hit more specific paths last. More specific
    // paths (e.g. foo/bar/baz.html) override entries for less-specific ones
    // (e.g. foo/bar).
    expectationsArray.sort(getAlphanumericCompare('path'));

    var allTests = getAllTests();
    for (var i = 0; i < expectationsArray.length; i++) {
      var path = expectationsArray[i].path;
      var expectations = expectationsArray[i].expectations;

      var pathMatchesAnyTest = false;
      if (allTests[path]) {
        pathMatchesAnyTest = true;
        addTestToAllExpectations(path, expectations);
      } else {
        for (var test in allTests) {
          if (stringContains(test, path)) {
            pathMatchesAnyTest = true;
            addTestToAllExpectations(test, expectations);
          }
        }
      }

      if (!pathMatchesAnyTest)
        addTestToAllExpectations(path, expectations);
    }
  }

  function processMissingTestsWithExpectations(builder, platform, buildType) {
    var noFailures = [];
    var skipped = [];

    var allTestsForPlatformAndBuildType =
        getAllTestsWithSamePlatformAndBuildType(platform, buildType);
    for (var test in allExpectations) {
      var expectations = getExpectations(test, platform, buildType);

      if (!expectations)
        continue;

      // Test has expectations, but no result in the builders results.
      // This means it's either SKIP or passes on all builds.
      if (!allTestsForPlatformAndBuildType[test] &&
          !stringContains(expectations.modifiers, 'WONTFIX')) {
        if (stringContains(expectations.modifiers, 'SKIP')) {
          skipped.push(test);
        } else if (!expectations.expectations.match(/^\s*PASS\s*$/)) {
          // Don't show tests expected to always pass. This is used in ways like
          // the following:
          // foo/bar = FAIL
          // foo/bar/baz.html = PASS
          noFailures.push({test: test, expectations: expectations.expectations,
              modifiers: expectations.modifiers});
        }
      }
    }

    perBuilderSkippedPaths[builder] = skipped.sort();
    perBuilderWithExpectationsButNoFailures[builder] = noFailures.sort();
  }

  function processTestResultsForBuilderAsync(builder) {
    setTimeout(function() {
        processTestRunsForBuilder(builder);
      }, 0);
  }

  function processTestRunsForAllBuilders() {
    for (var builder in builders)
      processTestRunsForBuilder(builder);
  }

  function processTestRunsForBuilder(builderName) {
    if (perBuilderFailures[builderName]) {
      appendNumFlakyTests(builderName);
      return;
    }

    var start = Date.now();
    numFlakyTestsPerBuilder[builderName] = 0;

    processExpectations();

    var buildInfo = getPlatformAndBuildType(builderName);
    var platform = buildInfo.platform;
    var buildType = buildInfo.buildType;
    processMissingTestsWithExpectations(builderName, platform, buildType);

    var failures = [];
    var allTestsForThisBuilder = resultsByBuilder[builderName].tests;

    for (var test in allTestsForThisBuilder) {
      var resultsForTest = createResultsObjectForTest(test, builderName);
      populateExpectationsData(resultsForTest);

      var rawTest = resultsByBuilder[builderName].tests[test];
      resultsForTest.rawTimes = rawTest.times;
      var rawResults = rawTest.results;
      resultsForTest.rawResults = rawResults;
      resultsForTest.flips = rawResults.length - 1;

      var times = resultsForTest.rawTimes;
      var numTimesSeen = 0;
      var numResultsSeen = 0;
      var resultsIndex = 0;
      var currentResult;
      for (var i = 0;
           i < times.length && numTimesSeen < getMaxResults();
           i++) {
        numTimesSeen += times[i][0];

        while (rawResults[resultsIndex] &&
               numTimesSeen > (numResultsSeen + rawResults[resultsIndex][0])) {
          numResultsSeen += rawResults[resultsIndex][0];
          resultsIndex++;
        }

        if (rawResults && rawResults[resultsIndex])
          currentResult = rawResults[resultsIndex][1];

        var time = times[i][1]

        // Ignore times for crashing/timeout runs for the sake of seeing if
        // a test should be marked slow.
        if (currentResult != 'C' && currentResult != 'T') {
          resultsForTest.slowestNonTimeoutCrashTime =
              Math.max(resultsForTest.slowestNonTimeoutCrashTime, time);
        }
        resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, time);
      }

      processMissingAndExtraExpectations(resultsForTest);

      if (resultsForTest.isFlaky)
        numFlakyTestsPerBuilder[builderName]++;

      failures.push(resultsForTest);

      if (!testToResultsMap[test])
        testToResultsMap[test] = [];
      testToResultsMap[test].push(resultsForTest);
    }

    appendNumFlakyTests(builderName);
    perBuilderFailures[builderName] = failures;
    logTime('processTestRunsForBuilder: ' + builderName, start);
  }

  function processMissingAndExtraExpectations(resultsForTest) {
    // Heuristic for determining whether expectations apply to a given test:
    // -If a test result happens < MIN_RUNS_FOR_FLAKE, then consider it a flaky
    // result and include it in the list of expected results.
    // -Otherwise, grab the first contiguous set of runs with the same result
    // for >= MIN_RUNS_FOR_FLAKE and ignore all following runs >=
    // MIN_RUNS_FOR_FLAKE.
    // This lets us rule out common cases of a test changing expectations for
    // a few runs, then being fixed or otherwise modified in a non-flaky way.
    var rawResults = resultsForTest.rawResults;

    // If the first result is no-data that means the test is skipped or is
    // being run on a different builder (e.g. moved from one shard to another).
    // Ignore these results since we have no real data about what's going on.
    if (rawResults[0][1] == 'N')
      return;

    // Only consider flake if it doesn't happen twice in a row.
    var MIN_RUNS_FOR_FLAKE = 2;
    var resultsMap = {}
    var numResultsSeen = 0;
    var haveSeenNonFlakeResult = false;
    var numRealResults = 0;
    var failedCount = 0;

    var seenResults = {};
    for (var i = 0;
         i < rawResults.length && numResultsSeen < getMaxResults();
         i++) {
      var numResults = rawResults[i][0];
      numResultsSeen += numResults;

      var result = rawResults[i][1];
      if (isFailingResult(result)) {
        failedCount += numResults;
      }

      var hasMinRuns = numResults >= MIN_RUNS_FOR_FLAKE;
      if (haveSeenNonFlakeResult && hasMinRuns) {
        continue;
      } else if (hasMinRuns) {
        haveSeenNonFlakeResult = true;
      } else if (!seenResults[result]) {
        // Only consider a short-lived result if we've seen it more than once.
        // Otherwise, we include lots of false-positives due to tests that fail
        // for a couple runs and then start passing.
        seenResults[result] = true;
        continue;
      }

      var expectation = getExpectationsFileStringForResult(result);
      resultsMap[expectation] = true;
      numRealResults++;
    }

    resultsForTest.isFlaky = numRealResults > 1;
    // Calculate the % of times the test failed - how flaky is it?
    resultsForTest.percentFailed =
        Math.round(failedCount / numResultsSeen * 100);

    var expectationsArray = resultsForTest.expectations ?
        resultsForTest.expectations.split(' ') : [];
    var extraExpectations = expectationsArray.filter(
        function(element) {
          // TODO(ojan): Once all the FAIL lines are removed from
          // test_expectations.txt, delete all the legacyExpectationsSemantics
          // code.
          if (currentState.legacyExpectationsSemantics) {
            if (element == 'FAIL') {
              for (var i = 0; i < FAIL_RESULTS.length; i++) {
                if (resultsMap[FAIL_RESULTS[i]])
                  return false;
              }
              return true;
            }
          }

          return element && !resultsMap[element] &&
              !stringContains(element, 'BUG');
        });

    var missingExpectations = [];
    for (var result in resultsMap) {
      var hasExpectation = false;
      for (var i = 0; i < expectationsArray.length; i++) {
        var expectation = expectationsArray[i];
        // TODO(ojan): Once all the FAIL lines are removed from
        // test_expectations.txt, delete all the legacyExpectationsSemantics
        // code.
        if (currentState.legacyExpectationsSemantics) {
          if (expectation == 'FAIL') {
            for (var j = 0; j < FAIL_RESULTS.length; j++) {
              if (result == FAIL_RESULTS[j]) {
                hasExpectation = true;
                break;
              }
            }
          }
        }

        if (result == expectation) {
          hasExpectation = true;
        }

        if (hasExpectation)
          break;
      }
      // If we have no expectations for a test and it only passes, then don't
      // list PASS as a missing expectation. We only want to list PASS if it
      // flaky passes, so there would be other expectations.
      if (!hasExpectation &&
          !(!expectationsArray.length && result == 'PASS' &&
            numRealResults == 1))
        missingExpectations.push(result);
    }

    // Only highlight tests that take > 2 seconds as needing to be marked as
    // slow. There are too many tests that take ~2 seconds every couple
    // hundred runs. It's not worth the manual maintenance effort.
    // Also, if a test times out, then it should not be marked as slow.
    var minTimeForNeedsSlow = isDebug(resultsForTest.builder) ? 2 : 1;
    if (isSlowTest(resultsForTest) && !resultsMap['TIMEOUT'] &&
        (!resultsForTest.modifiers ||
         !stringContains(resultsForTest.modifiers, 'SLOW'))) {
      missingExpectations.push('SLOW');
    } else if (isFastTest(resultsForTest) &&
               resultsForTest.modifiers &&
               stringContains(resultsForTest.modifiers, 'SLOW')) {
      extraExpectations.push('SLOW');
    }

    resultsForTest.meetsExpectations =
        !missingExpectations.length && !extraExpectations.length;
    resultsForTest.missing = missingExpectations.sort().join(' ');
    resultsForTest.extra = extraExpectations.sort().join(' ');
  }

  var bugUrlPrefix = '<a href="http://';
  var bugUrlPostfix = '/$1">$1</a> ';
  var internalBugReplaceValue = bugUrlPrefix + 'b' + bugUrlPostfix;
  var externalBugReplaceValue = bugUrlPrefix + 'crbug.com' + bugUrlPostfix;

  /**
   * Returns the BUG modifiers linking to the bug.
   * Bugs with 4 or 5 digits are crbug.com bugs. Bugs with 6 or 7 digits
   * are internal google bugs.
   */
  function getHtmlForBugs(bugs) {
    bugs = bugs.replace(/BUG(\d{4})(\ |$)/g, externalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{5})(\ |$)/g, externalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{6})(\ |$)/g, internalBugReplaceValue);
    bugs = bugs.replace(/BUG(\d{7})(\ |$)/g, internalBugReplaceValue);
    return bugs;
  }

  function getLinkHTMLToOpenWindow(url, text) {
    return '<div class=link onclick="window.open(\'' + url + '\')">' + text +
        '</div>';
  }

  function createBlameListHTML(revisions, index, urlBase, separator, repo) {
    var thisRevision = revisions[index];
    if (!thisRevision)
      return '';

    var previousRevision = revisions[index + 1];
    if (previousRevision && previousRevision != thisRevision) {
      previousRevision++;
      return getLinkHTMLToOpenWindow(
          urlBase + thisRevision + separator + previousRevision,
          repo + ' blamelist r' + previousRevision + ':r' + thisRevision);
    } else {
      return 'At ' + repo + ' revision: ' + thisRevision;
    }
  }

  function showPopupForBuild(e, builder, index) {
    var html = '';

    var time = resultsByBuilder[builder].secondsSinceEpoch[index];
    if (time) {
      var date = new Date(time * 1000);
      html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
    }

    html += '<ul><li>' +
      createBlameListHTML(resultsByBuilder[builder].webkitRevision, index,
          'http://trac.webkit.org/log/?rev=', '&stop_rev=', 'WebKit') +
      '</li><li>' +
      createBlameListHTML(resultsByBuilder[builder].chromeRevision, index,
          'http://build.chromium.org/buildbot/perf/dashboard/ui/' +
          'changelog.html?url=/trunk/src&mode=html&range=', ':', 'Chrome') +
      '</li>';

    var chromeRevision = resultsByBuilder[builder].chromeRevision[index];
    if (chromeRevision) {
      html += '<li><a href="' + TEST_RESULTS_BASE_PATH + builders[builder] +
          '/' + chromeRevision + '/layout-test-results.zip' +
          '">layout-test-results.zip</a></li>';
    }

    var buildNumbers = resultsByBuilder[builder].buildNumbers;
    html += '<li>' +
        getLinkHTMLToOpenWindow(BUILDERS_BASE_PATH + builder + '/builds/' +
        buildNumbers[index], 'Build log') + '</li></ul>';

    showPopup(e, html);
  }

  function showPopupForTest(e, test) {
    showPopup(e, getHTMLForIndividulTestOnAllBuilders(test));
    appendExpectations();
  }

  function getHtmlForTestResults(test) {
    var html = '';
    var results = test.rawResults.concat();
    var times = test.rawTimes.concat();
    var builder = test.builder;
    var buildNumbers = resultsByBuilder[builder].buildNumbers;

    var indexToReplaceCurrentResult = -1;
    var indexToReplaceCurrentTime = -1;
    var currentResultArray, currentTimeArray, currentResult, innerHTML,
        resultString;
    var maxIndex = Math.min(buildNumbers.length, getMaxResults());
    for (var i = 0; i < maxIndex; i++) {
      if (i > indexToReplaceCurrentResult) {
        currentResultArray = results.shift();
        if (currentResultArray) {
          currentResult = currentResultArray[1];
          // Treat simplified diff failures as just text failures.
          if (currentResult == 'S')
            currentResult = 'F';
          indexToReplaceCurrentResult += currentResultArray[0];
        } else {
          currentResult = 'N';
          indexToReplaceCurrentResult += buildNumbers.length;
        }

        resultString = getExpectationsFileStringForResult(currentResult);
      }

      if (i > indexToReplaceCurrentTime) {
        currentTimeArray = times.shift();
        var currentTime = 0;
        if (currentResultArray) {
          currentTime = currentTimeArray[1];
          indexToReplaceCurrentTime += currentTimeArray[0];
        } else {
          indexToReplaceCurrentTime += buildNumbers.length;
        }
        innerHTML = currentTime || '&nbsp;';
      }

      html += '<td title="' + resultString + '. Click for more info." ' +
          'class="results ' + currentResult +
          '" onclick=\'showPopupForBuild(event, "' + builder + '",' + i +
          ')\'>' + innerHTML + '</td>';

      var webkitRevision = resultsByBuilder[builder].webkitRevision;
      var isWebkitMerge = webkitRevision[i + 1] &&
          webkitRevision[i] != webkitRevision[i + 1];
      if (isWebkitMerge)
        html += '<td class=merge></td>';
    }
    return html;
  }

  function getHTMLForTestsWithExpectationsButNoFailures(builder) {
    var tests = perBuilderWithExpectationsButNoFailures[builder];
    var skippedPaths = perBuilderSkippedPaths[builder];

    var html = '';
    if (tests.length || skippedPaths.length) {
      var buildInfo = getPlatformAndBuildType(builder);
      html += '<h2>Expectations for ' + buildInfo.platform + '-' +
          buildInfo.buildType + ':</h2>';
    }

    var open = '<div onclick="selectContents(this)">';

    if (tests.length) {
      html +=  '<h3>Have not failed in last ' +
          resultsByBuilder[builder].buildNumbers.length +
          ' runs.</h3><div id="passing-tests">';

      for (var i = 0; i < tests.length; i++) {
        html += open + tests[i].test + '</div>';
      }

      html += '</div>';
    }

    if (skippedPaths.length) {
      html += '<h3>' +
          getLinkHTMLToToggleState('showSkipped',
              'Skipped tests in test_expectations.txt') +
          '</h3>';

      if (currentState.showSkipped) {
        html += '<div id="passing-tests">' + open +
          skippedPaths.join('</div>' + open) + '</div></div>';
      }
    }
    return html;
  }

  /**
   * Returns whether the test has passed continuously for at
   * least as many runs as we are showing results for.
   */
   function hasPassedInMaxRuns(testResult) {
     var results = testResult.rawResults;
     return results[0][1] == 'P' && results[0][0] >= getMaxResults();
   }

  /**
   * Returns whether we should exclude test results from the test table.
   */
  function shouldHideTest(testResult) {
    if (testResult.isWontFixSkip)
      return !currentState.showWontFixSkip;

    if (testResult.isFlaky)
      return !currentState.showFlaky;

    if (isSlowTest(testResult))
      return !currentState.showSlow;

    if (testResult.meetsExpectations)
      return !currentState.showCorrectExpectations;

    return !currentState.showWrongExpectations;
  }

  /**
   * Sets the browser's selection to the element's contents.
   */
  function selectContents(element) {
    window.getSelection().selectAllChildren(element);
  }
  
  function getCreateBugHTML(test) {
    var symptom = test.isFlaky ? 'flaky' : 'failing';
    var title = 'Layout Test ' + test.test + ' is ' + symptom;
    var description = 'The following layout test is ' + symptom + ' on ' +
        '[insert platform]\n\n' + test.test + '\n\nProbable cause:\n\n' +
        '[insert probable cause]';
    return bugUrlPrefix + 'code.google.com/p/chromium/issues/entry?' +
        'template=Layout%20Test%20Failure&summary=' + 
        encodeURIComponent(title) + '&comment=' + 
        encodeURIComponent(description) + '" class="file-bug">FILE BUG</a>';
  }

  function getHTMLForSingleTestRow(test, opt_isCrossBuilderView) {
    if (!opt_isCrossBuilderView && shouldHideTest(test)) {
      // The innerHTML call is considerably faster if we exclude the rows for
      // items we're not showing than if we hide them using display:none.
      // For the crossBuilderView, we want to show all rows the user is
      // explicitly listing tests to view.
      return '';
    }

    // If opt_isCrossBuilderView is true, we're just viewing a single test
    // with results for many builders, so the first column is builder names
    // instead of test paths.
    var testCellHTML = opt_isCrossBuilderView ? test.builder :
      '<span class="link" onclick="showPopupForTest(event, \'' + test.test +
      '\');selectContents(this);return false;">' + test.test + '</span>';

    return '<tr class="' +
      (test.meetsExpectations ? '' : 'wrong-expectations') +
      // TODO(ojan): If a test is a chrome/ or a pending/ test, point to
      // src.chromium.org instead of trac.webkit.org.
      '"><td class=test-link>' + testCellHTML +
      '</td><td class=options-container>' + 
      (test.bugsHTML ? test.bugsHTML : getCreateBugHTML(test)) +
      '</td><td class=options-container>' + test.modifiersHTML +
      '</td><td class=options-container>' + test.expectationsHTML +
      '</td><td>' + test.missing +
      '</td><td>' + test.extra +
      '</td><td>' + (test.slowestTime ? test.slowestTime + 's' : '') +
      '</td><td>' + test.percentFailed +
      '</td>' + getHtmlForTestResults(test) + '</tr>';
  }

  function getSortColumnFromTableHeader(headerText) {
    return headerText.split(' ', 1)[0];
  }

  function getHTMLForTestTable(rowsHTML) {
    var html = '<table class=test-table><thead><tr>';
    for (var i = 0; i < tableHeaders.length; i++) {
{'fraction_non_alphanumeric': 0.1207461958310166, 'fraction_numerical': 0.005197045173442872, 'mean_word_length': 2.4425, 'pattern_counts': {'":': 0, '<': 198, '<?xml version=': 0, '>': 185, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3132932', 'n_tokens_mistral': 26420, 'n_tokens_neox': 25303, 'n_words': 6561}