| Index: tools/chrome_proxy/integration_tests/chrome_proxy_metrics.py | 
| diff --git a/tools/chrome_proxy/integration_tests/chrome_proxy_metrics.py b/tools/chrome_proxy/integration_tests/chrome_proxy_metrics.py | 
| index a92ef1488f79631244f78bedd9a4523d8a7ead30..8857a56e4cff95393360195f389fa1e3a96bc157 100644 | 
| --- a/tools/chrome_proxy/integration_tests/chrome_proxy_metrics.py | 
| +++ b/tools/chrome_proxy/integration_tests/chrome_proxy_metrics.py | 
| @@ -2,12 +2,10 @@ | 
| # Use of this source code is governed by a BSD-style license that can be | 
| # found in the LICENSE file. | 
|  | 
| -import datetime | 
| import logging | 
| -import os | 
| +import time | 
|  | 
| from integration_tests import network_metrics | 
| -from telemetry.core import util | 
| from telemetry.page import page_test | 
| from telemetry.value import scalar | 
|  | 
| @@ -19,41 +17,6 @@ class ChromeProxyMetricException(page_test.MeasurementFailure): | 
| CHROME_PROXY_VIA_HEADER = 'Chrome-Compression-Proxy' | 
| CHROME_PROXY_VIA_HEADER_DEPRECATED = '1.1 Chrome Compression Proxy' | 
|  | 
| -PROXY_SETTING_HTTPS = 'proxy.googlezip.net:443' | 
| -PROXY_SETTING_HTTPS_WITH_SCHEME = 'https://' + PROXY_SETTING_HTTPS | 
| -PROXY_DEV_SETTING_HTTP = 'proxy-xt.googlezip.net:80' | 
| -PROXY_SETTING_HTTP = 'compress.googlezip.net:80' | 
| -PROXY_SETTING_DIRECT = 'direct://' | 
| - | 
| -# The default Chrome Proxy bypass time is a range from one to five mintues. | 
| -# See ProxyList::UpdateRetryInfoOnFallback in net/proxy/proxy_list.cc. | 
| -DEFAULT_BYPASS_MIN_SECONDS = 60 | 
| -DEFAULT_BYPASS_MAX_SECONDS = 5 * 60 | 
| - | 
| -def GetProxyInfoFromNetworkInternals(tab, url='chrome://net-internals#proxy'): | 
| -  tab.Navigate(url) | 
| -  with open(os.path.join(os.path.dirname(__file__), | 
| -                         'chrome_proxy_metrics.js')) as f: | 
| -    js = f.read() | 
| -    tab.ExecuteJavaScript(js) | 
| -  tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 300) | 
| - | 
| -  # Sometimes, the proxy information on net_internals#proxy is slow to come up. | 
| -  # In order to prevent this from causing tests to flake frequently, wait for | 
| -  # up to 10 seconds for this information to appear. | 
| -  def IsDataReductionProxyEnabled(): | 
| -    info = tab.EvaluateJavaScript('window.__getChromeProxyInfo()') | 
| -    return info['enabled'] | 
| - | 
| -  util.WaitFor(IsDataReductionProxyEnabled, 10) | 
| -  info = tab.EvaluateJavaScript('window.__getChromeProxyInfo()') | 
| -  return info | 
| - | 
| - | 
| -def ProxyRetryTimeInRange(retry_time, low, high, grace_seconds=30): | 
| -  return (retry_time >= low - datetime.timedelta(seconds=grace_seconds) and | 
| -          (retry_time < high + datetime.timedelta(seconds=grace_seconds))) | 
| - | 
|  | 
| class ChromeProxyResponse(network_metrics.HTTPResponse): | 
| """ Represents an HTTP response from a timeleine event.""" | 
| @@ -126,12 +89,6 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
| def __init__(self): | 
| super(ChromeProxyMetric, self).__init__() | 
| self.compute_data_saving = True | 
| -    self.effective_proxies = { | 
| -        "proxy": PROXY_SETTING_HTTPS_WITH_SCHEME, | 
| -        "proxy-dev": PROXY_DEV_SETTING_HTTP, | 
| -        "fallback": PROXY_SETTING_HTTP, | 
| -        "direct": PROXY_SETTING_DIRECT, | 
| -        } | 
|  | 
| def SetEvents(self, events): | 
| """Used for unittest.""" | 
| @@ -168,6 +125,7 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
|  | 
| def AddResultsForHeaderValidation(self, tab, results): | 
| via_count = 0 | 
| + | 
| for resp in self.IterResponses(tab): | 
| if resp.IsValidByViaHeader(): | 
| via_count += 1 | 
| @@ -233,110 +191,9 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
| results.AddValue(scalar.ScalarValue( | 
| results.current_page, 'bypass', 'count', bypass_count)) | 
|  | 
| -  def ProxyListForDev(self, proxies): | 
| -    return [self.effective_proxies['proxy-dev'] | 
| -            if proxy == self.effective_proxies['proxy'] | 
| -            else proxy for proxy in proxies] | 
| - | 
| -  def IsProxyBypassed(self, tab): | 
| -    """Get whether all configured proxies are bypassed. | 
| - | 
| -    Returns: | 
| -        A tuple of the form (boolean, string list). If all configured proxies | 
| -        are bypassed, then the return value will be (True, bypassed proxies). | 
| -        Otherwise, the return value will be (False, empty list). | 
| -    """ | 
| -    if not tab: | 
| -      return False, [] | 
| - | 
| -    info = GetProxyInfoFromNetworkInternals(tab) | 
| -    if not info['enabled']: | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Chrome proxy should be enabled. proxy info: %s' % info) | 
| -    if not info['badProxies']: | 
| -      return False, [] | 
| - | 
| -    bad_proxies = [str(p['proxy']) for p in info['badProxies']] | 
| -    # Expect all but the "direct://" proxy to be bad. | 
| -    expected_bad_proxies = info['proxies'][:-1] | 
| -    if set(bad_proxies) == set(expected_bad_proxies): | 
| -      return True, expected_bad_proxies | 
| -    return False, [] | 
| - | 
| -  def VerifyBadProxies(self, bad_proxies, expected_bad_proxies): | 
| -    """Verify the bad proxy list and their retry times are expected. | 
| - | 
| -    Args: | 
| -        bad_proxies: the list of actual bad proxies and their retry times. | 
| -        expected_bad_proxies: a list of dictionaries in the form: | 
| - | 
| -            {'proxy': <proxy origin>, | 
| -             'retry_seconds_low': <minimum bypass duration in seconds>, | 
| -             'retry_seconds_high': <maximum bypass duration in seconds>} | 
| - | 
| -            If an element in the list is missing either the 'retry_seconds_low' | 
| -            entry or the 'retry_seconds_high' entry, the default bypass minimum | 
| -            and maximum durations respectively will be used for that element. | 
| -    """ | 
| -    if not bad_proxies: | 
| -      bad_proxies = [] | 
| -    if len(bad_proxies) != len(expected_bad_proxies): | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Actual and expected bad proxy lists should match: %s vs. %s' % ( | 
| -              str(bad_proxies), str(expected_bad_proxies))) | 
| - | 
| -    # Check that each of the proxy origins and retry times match. | 
| -    for expected_bad_proxy in expected_bad_proxies: | 
| -      # Find a matching actual bad proxy origin, allowing for the proxy-dev | 
| -      # origin in the place of the HTTPS proxy origin. | 
| -      bad_proxy = None | 
| -      for actual_proxy in bad_proxies: | 
| -        if (expected_bad_proxy['proxy'] == actual_proxy['proxy'] or ( | 
| -            self.effective_proxies['proxy-dev'] == actual_proxy['proxy'] and | 
| -            self.effective_proxies['proxy'] == expected_bad_proxy['proxy'])): | 
| -          bad_proxy = actual_proxy | 
| -          break | 
| -      if not bad_proxy: | 
| -        raise ChromeProxyMetricException, ( | 
| -            'No match for expected bad proxy %s - actual and expected bad ' | 
| -            'proxies should match: %s vs. %s' % (expected_bad_proxy['proxy'], | 
| -                                                 str(bad_proxies), | 
| -                                                 str(expected_bad_proxies))) | 
| - | 
| -      # Check that the retry times match. | 
| -      retry_seconds_low = expected_bad_proxy.get('retry_seconds_low', | 
| -                                                 DEFAULT_BYPASS_MIN_SECONDS) | 
| -      retry_seconds_high = expected_bad_proxy.get('retry_seconds_high', | 
| -                                                  DEFAULT_BYPASS_MAX_SECONDS) | 
| -      retry_time_low = (datetime.datetime.now() + | 
| -                        datetime.timedelta(seconds=retry_seconds_low)) | 
| -      retry_time_high = (datetime.datetime.now() + | 
| -                         datetime.timedelta(seconds=retry_seconds_high)) | 
| -      got_retry_time = datetime.datetime.fromtimestamp( | 
| -          int(bad_proxy['retry'])/1000) | 
| -      if not ProxyRetryTimeInRange( | 
| -          got_retry_time, retry_time_low, retry_time_high): | 
| -        raise ChromeProxyMetricException, ( | 
| -            'Bad proxy %s retry time (%s) should be within range (%s-%s).' % ( | 
| -                bad_proxy['proxy'], str(got_retry_time), str(retry_time_low), | 
| -                str(retry_time_high))) | 
| - | 
| -  def VerifyAllProxiesBypassed(self, tab): | 
| -    """Verify that all proxies are bypassed for 1 to 5 minutes.""" | 
| -    if tab: | 
| -      info = GetProxyInfoFromNetworkInternals(tab) | 
| -      if not info['enabled']: | 
| -        raise ChromeProxyMetricException, ( | 
| -            'Chrome proxy should be enabled. proxy info: %s' % info) | 
| -      is_bypassed, expected_bad_proxies = self.IsProxyBypassed(tab) | 
| -      if not is_bypassed: | 
| -        raise ChromeProxyMetricException, ( | 
| -            'Chrome proxy should be bypassed. proxy info: %s' % info) | 
| -      self.VerifyBadProxies(info['badProxies'], | 
| -                            [{'proxy': p} for p in expected_bad_proxies]) | 
| - | 
| def AddResultsForBypass(self, tab, results): | 
| bypass_count = 0 | 
| + | 
| for resp in self.IterResponses(tab): | 
| if resp.HasChromeProxyViaHeader(): | 
| r = resp.response | 
| @@ -389,6 +246,7 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
| def AddResultsForBlockOnce(self, tab, results): | 
| eligible_response_count = 0 | 
| bypass_count = 0 | 
| + | 
| for resp in self.IterResponses(tab): | 
| if resp.ShouldHaveChromeProxyViaHeader(): | 
| eligible_response_count += 1 | 
| @@ -415,6 +273,7 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
| def AddResultsForSafebrowsing(self, tab, results): | 
| count = 0 | 
| safebrowsing_count = 0 | 
| + | 
| for resp in self.IterResponses(tab): | 
| count += 1 | 
| if resp.IsSafebrowsingResponse(): | 
| @@ -433,76 +292,130 @@ class ChromeProxyMetric(network_metrics.NetworkMetric): | 
| 'Safebrowsing failed (count=%d, safebrowsing_count=%d)\n' % ( | 
| count, safebrowsing_count)) | 
|  | 
| -  def VerifyProxyInfo(self, tab, expected_proxies, expected_bad_proxies): | 
| -    info = GetProxyInfoFromNetworkInternals(tab) | 
| -    if not 'enabled' in info or not info['enabled']: | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Chrome proxy should be enabled. proxy info: %s' % info) | 
| -    proxies = info['proxies'] | 
| -    if (set(proxies) != set(expected_proxies) and | 
| -        set(proxies) != set(self.ProxyListForDev(expected_proxies))): | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Wrong effective proxies (%s). Expect: "%s"' % ( | 
| -          str(proxies), str(expected_proxies))) | 
| - | 
| -    bad_proxies = [] | 
| -    if 'badProxies' in info and info['badProxies']: | 
| -      bad_proxies = [p['proxy'] for p in info['badProxies'] | 
| -                     if 'proxy' in p and p['proxy']] | 
| -    if (set(bad_proxies) != set(expected_bad_proxies) and | 
| -        set(bad_proxies) != set(self.ProxyListForDev(expected_bad_proxies))): | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Wrong bad proxies (%s). Expect: "%s"' % ( | 
| -          str(bad_proxies), str(expected_bad_proxies))) | 
| - | 
| -  def AddResultsForHTTPFallback( | 
| -      self, tab, results, expected_proxies=None, expected_bad_proxies=None): | 
| -    if not expected_proxies: | 
| -      expected_proxies = [self.effective_proxies['fallback'], | 
| -                          self.effective_proxies['direct']] | 
| -    if not expected_bad_proxies: | 
| -      expected_bad_proxies = [] | 
| - | 
| -    # TODO(sclittle): Remove this dependency on net-internals#proxy once an | 
| -    # alternative method of verifying that Chrome is on the fallback proxy | 
| -    # exists. | 
| -    self.VerifyProxyInfo(tab, expected_proxies, expected_bad_proxies) | 
| +  def AddResultsForHTTPFallback(self, tab, results): | 
| +    via_fallback_count = 0 | 
| + | 
| +    for resp in self.IterResponses(tab): | 
| +      if resp.ShouldHaveChromeProxyViaHeader(): | 
| +        # All responses should have come through the HTTP fallback proxy, which | 
| +        # means that they should have the via header, and if a remote port is | 
| +        # defined, it should be port 80. | 
| +        if (not resp.HasChromeProxyViaHeader() or | 
| +            (resp.remote_port and resp.remote_port != 80)): | 
| +          r = resp.response | 
| +          raise ChromeProxyMetricException, ( | 
| +              '%s: Should have come through the fallback proxy.\n' | 
| +              'Reponse: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % ( | 
| +                  r.url, str(resp.remote_port), r.status, r.status_text, | 
| +                  r.headers)) | 
| +        via_fallback_count += 1 | 
| + | 
| results.AddValue(scalar.ScalarValue( | 
| -        results.current_page, 'http_fallback', 'boolean', True)) | 
| +        results.current_page, 'via_fallback', 'count', via_fallback_count)) | 
|  | 
| def AddResultsForHTTPToDirectFallback(self, tab, results): | 
| +    via_fallback_count = 0 | 
| bypass_count = 0 | 
| -    for resp in self.IterResponses(tab): | 
| +    responses = self.IterResponses(tab) | 
| + | 
| +    # The very first response should be through the HTTP fallback proxy. | 
| +    fallback_resp = next(responses, None) | 
| +    if not fallback_resp: | 
| +      raise ChromeProxyMetricException, 'There should be at least one response.' | 
| +    elif (not fallback_resp.HasChromeProxyViaHeader() or | 
| +          fallback_resp.remote_port != 80): | 
| +      r = fallback_resp.response | 
| +      raise ChromeProxyMetricException, ( | 
| +          'Response for %s should have come through the fallback proxy.\n' | 
| +          'Reponse: remote_port=%s status=(%d, %s)\nHeaders:\n %s' % ( | 
| +              r.url, str(fallback_resp.remote_port), r.status, r.status_text, | 
| +              r.headers)) | 
| +    else: | 
| +      via_fallback_count += 1 | 
| + | 
| +    # All other responses should have been bypassed. | 
| +    for resp in responses: | 
| if resp.HasChromeProxyViaHeader(): | 
| r = resp.response | 
| raise ChromeProxyMetricException, ( | 
| -            'Response for %s should not have via header. ' | 
| +            'Response for %s should not have via header.\n' | 
| 'Reponse: status=(%d, %s)\nHeaders:\n %s' % ( | 
| r.url, r.status, r.status_text, r.headers)) | 
| else: | 
| bypass_count += 1 | 
|  | 
| results.AddValue(scalar.ScalarValue( | 
| +        results.current_page, 'via_fallback', 'count', via_fallback_count)) | 
| +    results.AddValue(scalar.ScalarValue( | 
| results.current_page, 'bypass', 'count', bypass_count)) | 
|  | 
| -  def AddResultsForExplicitBypass(self, tab, results, expected_bad_proxies): | 
| -    """Verify results for an explicit bypass test. | 
| +  def AddResultsForReenableAfterBypass( | 
| +      self, tab, results, bypass_seconds_min, bypass_seconds_max): | 
| +    """Verify results for a re-enable after bypass test. | 
|  | 
| Args: | 
| tab: the tab for the test. | 
| results: the results object to add the results values to. | 
| -        expected_bad_proxies: A list of dictionary objects representing | 
| -            expected bad proxies and their expected retry time windows. | 
| -            See the definition of VerifyBadProxies for details. | 
| +        bypass_seconds_min: the minimum duration of the bypass. | 
| +        bypass_seconds_max: the maximum duration of the bypass. | 
| """ | 
| -    info = GetProxyInfoFromNetworkInternals(tab) | 
| -    if not 'enabled' in info or not info['enabled']: | 
| -      raise ChromeProxyMetricException, ( | 
| -          'Chrome proxy should be enabled. proxy info: %s' % info) | 
| -    # TODO(sclittle): Remove this dependency on net-internals#proxy once an | 
| -    # alternative method of verifying that Chrome is on the fallback proxy | 
| -    # exists. | 
| -    self.VerifyBadProxies(info['badProxies'], | 
| -                          expected_bad_proxies) | 
| +    bypass_count = 0 | 
| +    via_count = 0 | 
| + | 
| +    for resp in self.IterResponses(tab): | 
| +      if resp.HasChromeProxyViaHeader(): | 
| +        r = resp.response | 
| +        raise ChromeProxyMetricException, ( | 
| +            'Response for %s should not have via header.\n' | 
| +            'Reponse: status=(%d, %s)\nHeaders:\n %s' % ( | 
| +                r.url, r.status, r.status_text, r.headers)) | 
| +      else: | 
| +        bypass_count += 1 | 
| + | 
| +    # Wait until 30 seconds before the bypass should expire, and fetch a page. | 
| +    # It should not have the via header because the proxy should still be | 
| +    # bypassed. | 
| +    time.sleep(bypass_seconds_min - 30) | 
| + | 
| +    tab.ClearCache(force=True) | 
| +    before_metrics = ChromeProxyMetric() | 
| +    before_metrics.Start(results.current_page, tab) | 
| +    tab.Navigate('http://chromeproxy-test.appspot.com/default') | 
| +    tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10) | 
| +    before_metrics.Stop(results.current_page, tab) | 
| + | 
| +    for resp in before_metrics.IterResponses(tab): | 
| +      if resp.HasChromeProxyViaHeader(): | 
| +        r = resp.response | 
| +        raise ChromeProxyMetricException, ( | 
| +            'Response for %s should not have via header; proxy should still ' | 
| +            'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % ( | 
| +                r.url, r.status, r.status_text, r.headers)) | 
| +      else: | 
| +        bypass_count += 1 | 
| + | 
| +    # Wait until 30 seconds after the bypass should expire, and fetch a page. It | 
| +    # should have the via header since the proxy should no longer be bypassed. | 
| +    time.sleep((bypass_seconds_max + 30) - (bypass_seconds_min - 30)) | 
| + | 
| +    tab.ClearCache(force=True) | 
| +    after_metrics = ChromeProxyMetric() | 
| +    after_metrics.Start(results.current_page, tab) | 
| +    tab.Navigate('http://chromeproxy-test.appspot.com/default') | 
| +    tab.WaitForJavaScriptExpression('performance.timing.loadEventStart', 10) | 
| +    after_metrics.Stop(results.current_page, tab) | 
| + | 
| +    for resp in after_metrics.IterResponses(tab): | 
| +      if not resp.HasChromeProxyViaHeader(): | 
| +        r = resp.response | 
| +        raise ChromeProxyMetricException, ( | 
| +            'Response for %s should have via header; proxy should no longer ' | 
| +            'be bypassed.\nReponse: status=(%d, %s)\nHeaders:\n %s' % ( | 
| +                r.url, r.status, r.status_text, r.headers)) | 
| +      else: | 
| +        via_count += 1 | 
| + | 
| +    results.AddValue(scalar.ScalarValue( | 
| +        results.current_page, 'bypass', 'count', bypass_count)) | 
| results.AddValue(scalar.ScalarValue( | 
| -        results.current_page, 'explicit_bypass', 'boolean', True)) | 
| +        results.current_page, 'via', 'count', via_count)) | 
|  |