2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
7 from copy
import deepcopy
8 from cStringIO
import StringIO
9 from functools
import partial
10 from hashlib
import sha1
11 from random
import random
13 from zipfile
import ZipFile
15 from caching_file_system
import CachingFileSystem
16 from file_system
import FileNotFoundError
, StatInfo
17 from fake_url_fetcher
import FakeURLFSFetcher
, MockURLFetcher
18 from local_file_system
import LocalFileSystem
19 from new_github_file_system
import GithubFileSystem
20 from object_store_creator
import ObjectStoreCreator
21 from test_file_system
import TestFileSystem
24 class _TestBundle(object):
25 '''Bundles test file data with a GithubFileSystem and test utilites. Create
26 GithubFileSystems via |CreateGfs()|, the Fetcher it uses as |fetcher|,
27 randomly mutate its contents via |Mutate()|, and access the underlying zip
34 'zipfile/hello.txt': 'world',
35 'zipfile/readme': 'test zip',
36 'zipfile/dir/file1': 'contents',
37 'zipfile/dir/file2': 'more contents'
43 'HEAD': self
._MakeShaJson
(self
._GenerateHash
())
45 'zipball': self
._ZipFromFiles
(self
.files
)
50 self
._fake
_fetcher
= None
53 def CreateGfsAndFetcher(self
):
55 def create_mock_url_fetcher(base_path
):
57 # Save this reference so we can replace the TestFileSystem in Mutate.
58 self
._fake
_fetcher
= FakeURLFSFetcher(
59 TestFileSystem(self
._test
_files
), base_path
)
60 fetchers
.append(MockURLFetcher(self
._fake
_fetcher
))
63 # Constructing |gfs| will create a fetcher.
64 gfs
= GithubFileSystem
.ForTest(
65 'changing-repo/', create_mock_url_fetcher
, path
='')
66 assert len(fetchers
) == 1
67 return gfs
, fetchers
[0]
70 fake_version
= self
._GenerateHash
()
71 fake_data
= self
._GenerateHash
()
72 self
.files
['zipfile/hello.txt'] = fake_data
73 self
.files
['zipfile/new-file'] = fake_data
74 self
.files
['zipfile/dir/file1'] = fake_data
75 self
._test
_files
['test_owner']['changing-repo']['zipball'] = (
76 self
._ZipFromFiles
(self
.files
))
77 self
._test
_files
['test_owner']['changing-repo']['commits']['HEAD'] = (
78 self
._MakeShaJson
(fake_version
))
80 # Update the file_system used by FakeURLFSFetcher so the above mutations
82 self
._fake
_fetcher
.UpdateFS(TestFileSystem(self
._test
_files
))
84 return fake_version
, fake_data
86 def _GenerateHash(self
):
87 '''Generates an arbitrary SHA1 hash.
89 return sha1(str(random())).hexdigest()
91 def _MakeShaJson(self
, hash_value
):
92 commit_json
= json
.loads(deepcopy(LocalFileSystem('').ReadSingle(
93 'test_data/github_file_system/test_owner/repo/commits/HEAD').Get()))
94 commit_json
['sha'] = hash_value
95 return json
.dumps(commit_json
)
97 def _ZipFromFiles(self
, file_dict
):
99 zipfile
= ZipFile(string
, 'w')
100 for filename
, contents
in file_dict
.iteritems():
101 zipfile
.writestr(filename
, contents
)
103 return string
.getvalue()
106 class TestGithubFileSystem(unittest
.TestCase
):
108 self
._gfs
= GithubFileSystem
.ForTest(
109 'repo/', partial(FakeURLFSFetcher
, LocalFileSystem('')))
110 # Start and finish the repository load.
111 self
._cgfs
= CachingFileSystem(self
._gfs
, ObjectStoreCreator
.ForTest())
113 def testReadDirectory(self
):
114 self
._gfs
.Refresh().Get()
116 sorted(['requirements.txt', '.gitignore', 'README.md', 'src/']),
117 sorted(self
._gfs
.ReadSingle('').Get()))
119 sorted(['__init__.notpy', 'hello.notpy']),
120 sorted(self
._gfs
.ReadSingle('src/').Get()))
122 def testReadFile(self
):
123 self
._gfs
.Refresh().Get()
125 '# Compiled Python files\n'
128 self
.assertEqual(expected
, self
._gfs
.ReadSingle('.gitignore').Get())
130 def testMultipleReads(self
):
131 self
._gfs
.Refresh().Get()
133 self
._gfs
.ReadSingle('requirements.txt').Get(),
134 self
._gfs
.ReadSingle('requirements.txt').Get())
137 self
._gfs
.Refresh().Get()
139 'src/': sorted(['hello.notpy', '__init__.notpy']),
140 '': sorted(['requirements.txt', '.gitignore', 'README.md', 'src/'])
143 read
= self
._gfs
.Read(['', 'src/']).Get()
144 self
.assertEqual(expected
['src/'], sorted(read
['src/']))
145 self
.assertEqual(expected
[''], sorted(read
['']))
148 # This is the hash value from the zip on disk.
149 real_hash
= 'c36fc23688a9ec9e264d3182905dc0151bfff7d7'
151 self
._gfs
.Refresh().Get()
152 dir_stat
= StatInfo(real_hash
, {
153 'hello.notpy': StatInfo(real_hash
),
154 '__init__.notpy': StatInfo(real_hash
)
157 self
.assertEqual(StatInfo(real_hash
), self
._gfs
.Stat('README.md'))
158 self
.assertEqual(StatInfo(real_hash
), self
._gfs
.Stat('src/hello.notpy'))
159 self
.assertEqual(dir_stat
, self
._gfs
.Stat('src/'))
161 def testBadReads(self
):
162 self
._gfs
.Refresh().Get()
163 self
.assertRaises(FileNotFoundError
, self
._gfs
.Stat
, 'DONT_README.md')
164 self
.assertRaises(FileNotFoundError
,
165 self
._gfs
.ReadSingle('DONT_README.md').Get
)
167 def testCachingFileSystem(self
):
168 self
._cgfs
.Refresh().Get()
169 initial_cgfs_read_one
= self
._cgfs
.ReadSingle('src/hello.notpy').Get()
171 self
.assertEqual(initial_cgfs_read_one
,
172 self
._gfs
.ReadSingle('src/hello.notpy').Get())
173 self
.assertEqual(initial_cgfs_read_one
,
174 self
._cgfs
.ReadSingle('src/hello.notpy').Get())
176 initial_cgfs_read_two
= self
._cgfs
.Read(
177 ['README.md', 'requirements.txt']).Get()
180 initial_cgfs_read_two
,
181 self
._gfs
.Read(['README.md', 'requirements.txt']).Get())
183 initial_cgfs_read_two
,
184 self
._cgfs
.Read(['README.md', 'requirements.txt']).Get())
186 def testWithoutRefresh(self
):
187 # Without refreshing it will still read the content from blobstore, and it
188 # does this via the magic of the FakeURLFSFetcher.
189 self
.assertEqual(['__init__.notpy', 'hello.notpy'],
190 sorted(self
._gfs
.ReadSingle('src/').Get()))
192 def testRefresh(self
):
193 test_bundle
= _TestBundle()
194 gfs
, fetcher
= test_bundle
.CreateGfsAndFetcher()
196 # It shouldn't fetch until Refresh does so; then it will do 2, one for the
197 # stat, and another for the read.
198 self
.assertTrue(*fetcher
.CheckAndReset())
200 self
.assertTrue(*fetcher
.CheckAndReset(fetch_count
=1,
202 fetch_resolve_count
=1))
204 # Refresh is just an alias for Read('').
206 self
.assertTrue(*fetcher
.CheckAndReset())
208 initial_dir_read
= sorted(gfs
.ReadSingle('').Get())
209 initial_file_read
= gfs
.ReadSingle('dir/file1').Get()
211 version
, data
= test_bundle
.Mutate()
213 # Check that changes have not effected the file system yet.
214 self
.assertEqual(initial_dir_read
, sorted(gfs
.ReadSingle('').Get()))
215 self
.assertEqual(initial_file_read
, gfs
.ReadSingle('dir/file1').Get())
216 self
.assertNotEqual(StatInfo(version
), gfs
.Stat(''))
218 gfs
, fetcher
= test_bundle
.CreateGfsAndFetcher()
220 self
.assertTrue(*fetcher
.CheckAndReset(fetch_count
=1,
222 fetch_resolve_count
=1))
224 # Check that the changes have affected the file system.
225 self
.assertEqual(data
, gfs
.ReadSingle('new-file').Get())
226 self
.assertEqual(test_bundle
.files
['zipfile/dir/file1'],
227 gfs
.ReadSingle('dir/file1').Get())
228 self
.assertEqual(StatInfo(version
), gfs
.Stat('new-file'))
230 # Regression test: ensure that reading the data after it's been mutated,
231 # but before Refresh() has been realised, still returns the correct data.
232 gfs
, fetcher
= test_bundle
.CreateGfsAndFetcher()
233 version
, data
= test_bundle
.Mutate()
235 refresh_future
= gfs
.Refresh()
236 self
.assertTrue(*fetcher
.CheckAndReset(fetch_count
=1, fetch_async_count
=1))
238 self
.assertEqual(data
, gfs
.ReadSingle('new-file').Get())
239 self
.assertEqual(test_bundle
.files
['zipfile/dir/file1'],
240 gfs
.ReadSingle('dir/file1').Get())
241 self
.assertEqual(StatInfo(version
), gfs
.Stat('new-file'))
244 self
.assertTrue(*fetcher
.CheckAndReset(fetch_resolve_count
=1))
246 def testGetThenRefreshOnStartup(self
):
247 # Regression test: Test that calling Get() but never resolving the future,
248 # then Refresh()ing the data, causes the data to be refreshed.
249 test_bundle
= _TestBundle()
250 gfs
, fetcher
= test_bundle
.CreateGfsAndFetcher()
251 self
.assertTrue(*fetcher
.CheckAndReset())
253 # Get a predictable version.
254 version
, data
= test_bundle
.Mutate()
256 read_future
= gfs
.ReadSingle('hello.txt')
257 # Fetch for the Stat(), async-fetch for the Read().
258 self
.assertTrue(*fetcher
.CheckAndReset(fetch_count
=1, fetch_async_count
=1))
260 refresh_future
= gfs
.Refresh()
261 self
.assertTrue(*fetcher
.CheckAndReset())
263 self
.assertEqual(data
, read_future
.Get())
264 self
.assertTrue(*fetcher
.CheckAndReset(fetch_resolve_count
=1))
265 self
.assertEqual(StatInfo(version
), gfs
.Stat('hello.txt'))
266 self
.assertTrue(*fetcher
.CheckAndReset())
268 # The fetch will already have been resolved, so resolving the Refresh won't
271 self
.assertTrue(*fetcher
.CheckAndReset())
273 # Read data should not have changed.
274 self
.assertEqual(data
, gfs
.ReadSingle('hello.txt').Get())
275 self
.assertEqual(StatInfo(version
), gfs
.Stat('hello.txt'))
276 self
.assertTrue(*fetcher
.CheckAndReset())
279 if __name__
== '__main__':