15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import posixpath
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom future import Future
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from path_util import AssertIsDirectory, IsDirectory
104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class _Response(object):
134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __init__(self, content=''):
144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self.content = content
15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.headers = {'Content-Type': 'none'}
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.status_code = 200
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class FakeUrlFetcher(object):
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, base_path):
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._base_path = base_path
224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # Mock capabilities. Perhaps this class should be MockUrlFetcher.
234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._sync_count = 0
244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._async_count = 0
254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._async_resolve_count = 0
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ReadFile(self, filename):
28e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    # Fake DownloadError, the error that appengine usually raises.
29e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    class DownloadError(Exception): pass
30e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    try:
31e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch      with open(os.path.join(self._base_path, filename), 'r') as f:
32e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch        return f.read()
33e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    except IOError as e:
34e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch      raise DownloadError(e)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ListDir(self, directory):
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # In some tests, we need to test listing a directory from the HTML returned
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # from SVN. This reads an HTML file that has the directories HTML.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not os.path.isdir(os.path.join(self._base_path, directory)):
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._ReadFile(directory[:-1])
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    files = os.listdir(os.path.join(self._base_path, directory))
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    html = '<html><title>Revision: 00000</title>\n'
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for filename in files:
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if filename.startswith('.'):
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if os.path.isdir(os.path.join(self._base_path, directory, filename)):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        html += '<a>' + filename + '/</a>\n'
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        html += '<a>' + filename + '</a>\n'
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    html += '</html>'
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return html
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def FetchAsync(self, url):
544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._async_count += 1
55a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    url = url.rsplit('?', 1)[0]
564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    def resolve():
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self._async_resolve_count += 1
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return self._DoFetch(url)
59effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return Future(callback=resolve)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Fetch(self, url):
624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._sync_count += 1
634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return self._DoFetch(url)
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def _DoFetch(self, url):
66a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    url = url.rsplit('?', 1)[0]
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    result = _Response()
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if IsDirectory(url):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      result.content = self._ListDir(url)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      result.content = self._ReadFile(url)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return result
734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def CheckAndReset(self, sync_count=0, async_count=0, async_resolve_count=0):
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''Returns a tuple (success, error). Use in tests like:
764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self.assertTrue(*fetcher.CheckAndReset(...))
774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    '''
784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    errors = []
794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    for desc, expected, actual in (
804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        ('sync_count', sync_count, self._sync_count),
814e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        ('async_count', async_count, self._async_count),
824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        ('async_resolve_count', async_resolve_count,
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                self._async_resolve_count)):
844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      if actual != expected:
854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        errors.append('%s: expected %s got %s' % (desc, expected, actual))
864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    try:
874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return (len(errors) == 0, ', '.join(errors))
884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    finally:
894e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      self.Reset()
904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def Reset(self):
924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._sync_count = 0
934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._async_count = 0
944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._async_resolve_count = 0
954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)class FakeURLFSFetcher(object):
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  '''Use a file_system to resolve fake fetches. Mimics the interface of Google
994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Appengine's urlfetch.
1004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  '''
1014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __init__(self, file_system, base_path):
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    AssertIsDirectory(base_path)
1044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._base_path = base_path
1054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._file_system = file_system
1064e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1074e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def FetchAsync(self, url, **kwargs):
1084e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return Future(value=self.Fetch(url))
1094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def Fetch(self, url, **kwargs):
1114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return _Response(self._file_system.ReadSingle(
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        posixpath.join(self._base_path, url)).Get())
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def UpdateFS(self, file_system, base_path=None):
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    '''Replace the underlying FileSystem used to reslove URLs.
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    '''
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._file_system = file_system
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._base_path = base_path or self._base_path
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
121f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class MockURLFetcher(object):
122f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def __init__(self, fetcher):
123f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetcher = fetcher
124f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.Reset()
125f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
126f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def Fetch(self, url, **kwargs):
127f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetch_count += 1
128f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return self._fetcher.Fetch(url, **kwargs)
129f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
130f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def FetchAsync(self, url, **kwargs):
131f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetch_async_count += 1
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def next(result):
133f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self._fetch_resolve_count += 1
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return result
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return self._fetcher.FetchAsync(url, **kwargs).Then(next)
136f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
137f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def CheckAndReset(self,
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    fetch_count=0,
139f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    fetch_async_count=0,
140f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    fetch_resolve_count=0):
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    errors = []
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    for desc, expected, actual in (
143f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        ('fetch_count', fetch_count, self._fetch_count),
144f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        ('fetch_async_count', fetch_async_count, self._fetch_async_count),
145f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        ('fetch_resolve_count', fetch_resolve_count,
146f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                self._fetch_resolve_count)):
147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if actual != expected:
148f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        errors.append('%s: expected %s got %s' % (desc, expected, actual))
149f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    try:
150f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return (len(errors) == 0, ', '.join(errors))
151f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    finally:
152f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.Reset()
153f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
154f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def Reset(self):
155f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetch_count = 0
156f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetch_async_count = 0
157f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._fetch_resolve_count = 0
158