193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# Copyright 2014 The Chromium Authors. All rights reserved.
293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# Use of this source code is governed by a BSD-style license that can be
393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# found in the LICENSE file.
4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
5f516a629824f0019315b9841dc2df76dc7862ddeChris Craikimport codecs
693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport os
793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport sys
893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport collections
993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport StringIO
1093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
11b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
1293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikclass WithableStringIO(StringIO.StringIO):
13b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
1493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __enter__(self, *args):
1593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return self
1693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
1793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __exit__(self, *args):
1893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    pass
1993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
20b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
2193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikclass FakeFS(object):
22b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
2393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __init__(self, initial_filenames_and_contents=None):
2493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._file_contents = {}
2593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if initial_filenames_and_contents:
26b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      for k, v in initial_filenames_and_contents.iteritems():
2793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        self._file_contents[k] = v
2893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
2993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._bound = False
30f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    self._real_codecs_open = codecs.open
3193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._real_open = sys.modules['__builtin__'].open
32b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    self._real_abspath = os.path.abspath
3393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._real_exists = os.path.exists
3493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._real_walk = os.walk
3593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._real_listdir = os.listdir
3693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
3793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __enter__(self):
3893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.Bind()
3993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return self
4093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
4193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __exit__(self, *args):
4293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.Unbind()
4393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
4493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def Bind(self):
4593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    assert not self._bound
46f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    codecs.open = self._FakeCodecsOpen
4793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    sys.modules['__builtin__'].open = self._FakeOpen
48b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    os.path.abspath = self._FakeAbspath
4993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.path.exists = self._FakeExists
5093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.walk = self._FakeWalk
5193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.listdir = self._FakeListDir
5293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._bound = True
5393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
5493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def Unbind(self):
5593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    assert self._bound
56f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    codecs.open = self._real_codecs_open
5793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    sys.modules['__builtin__'].open = self._real_open
58b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    os.path.abspath = self._real_abspath
5993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.path.exists = self._real_exists
6093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.walk = self._real_walk
6193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    os.listdir = self._real_listdir
6293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._bound = False
6393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
6493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def AddFile(self, path, contents):
6593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    assert path not in self._file_contents
66b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    path = os.path.normpath(path)
6793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self._file_contents[path] = contents
6893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
6993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def _FakeOpen(self, path, mode=None):
70b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if mode is None:
7193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      mode = 'r'
7293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if mode == 'r' or mode == 'rU' or mode == 'rb':
7393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if path not in self._file_contents:
7493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        return self._real_open(path, mode)
7593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return WithableStringIO(self._file_contents[path])
7693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
7793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    raise NotImplementedError()
7893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
79b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  def _FakeCodecsOpen(self, path, mode=None,
80b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                      encoding=None):  # pylint: disable=unused-argument
81b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if mode is None:
82f516a629824f0019315b9841dc2df76dc7862ddeChris Craik      mode = 'r'
83f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    if mode == 'r' or mode == 'rU' or mode == 'rb':
84f516a629824f0019315b9841dc2df76dc7862ddeChris Craik      if path not in self._file_contents:
85f516a629824f0019315b9841dc2df76dc7862ddeChris Craik        return self._real_open(path, mode)
86f516a629824f0019315b9841dc2df76dc7862ddeChris Craik      return WithableStringIO(self._file_contents[path])
87f516a629824f0019315b9841dc2df76dc7862ddeChris Craik
88f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    raise NotImplementedError()
89f516a629824f0019315b9841dc2df76dc7862ddeChris Craik
90b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  def _FakeAbspath(self, path):
91b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """Normalize the path and ensure it starts with os.path.sep.
92b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
93b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    The tests all assume paths start with things like '/my/project',
94b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    and this abspath implementaion makes that assumption work correctly
95b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    on Windows.
96b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
97b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    normpath = os.path.normpath(path)
98b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not normpath.startswith(os.path.sep):
99b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      normpath = os.path.sep + normpath
100b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return normpath
101b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
10293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def _FakeExists(self, path):
10393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if path in self._file_contents:
10493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return True
10593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return self._real_exists(path)
10693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
10793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def _FakeWalk(self, top):
10893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    assert os.path.isabs(top)
10993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    all_filenames = self._file_contents.keys()
11093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    pending_prefixes = collections.deque()
11193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    pending_prefixes.append(top)
11293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    visited_prefixes = set()
11393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    while len(pending_prefixes):
11493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      prefix = pending_prefixes.popleft()
11593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if prefix in visited_prefixes:
11693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        continue
11793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      visited_prefixes.add(prefix)
118b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      if prefix.endswith(os.path.sep):
11993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        prefix_with_trailing_sep = prefix
12093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      else:
121b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        prefix_with_trailing_sep = prefix + os.path.sep
12293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
12393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      dirs = set()
12493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      files = []
12593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      for filename in all_filenames:
12693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        if not filename.startswith(prefix_with_trailing_sep):
12793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          continue
12893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        relative_to_prefix = os.path.relpath(filename, prefix)
12993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
13093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        dirpart = os.path.dirname(relative_to_prefix)
13193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        if len(dirpart) == 0:
13293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          files.append(relative_to_prefix)
13393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          continue
134b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        parts = dirpart.split(os.sep)
13593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        if len(parts) == 0:
13693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          dirs.add(dirpart)
13793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        else:
138b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          pending = os.path.join(prefix, parts[0])
13993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          dirs.add(parts[0])
14093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik          pending_prefixes.appendleft(pending)
14193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
14293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      dirs = list(dirs)
14393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      dirs.sort()
14493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      yield prefix, dirs, files
14593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
14693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def _FakeListDir(self, dirname):
14793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    raise NotImplementedError()
148