| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import json | 6 import json |
| 7 from copy import deepcopy |
| 7 from cStringIO import StringIO | 8 from cStringIO import StringIO |
| 8 from functools import partial | 9 from functools import partial |
| 9 from hashlib import sha1 | 10 from hashlib import sha1 |
| 10 from random import random | 11 from random import random |
| 11 import unittest | 12 import unittest |
| 12 from zipfile import ZipFile | 13 from zipfile import ZipFile |
| 13 | 14 |
| 14 from caching_file_system import CachingFileSystem | 15 from caching_file_system import CachingFileSystem |
| 15 from file_system import FileNotFoundError, StatInfo | 16 from file_system import FileNotFoundError, StatInfo |
| 16 from fake_url_fetcher import FakeURLFSFetcher, MockURLFetcher | 17 from fake_url_fetcher import FakeURLFSFetcher, MockURLFetcher |
| 17 from local_file_system import LocalFileSystem | 18 from local_file_system import LocalFileSystem |
| 18 from new_github_file_system import GithubFileSystem | 19 from new_github_file_system import GithubFileSystem |
| 19 from object_store_creator import ObjectStoreCreator | 20 from object_store_creator import ObjectStoreCreator |
| 20 from test_file_system import TestFileSystem | 21 from test_file_system import TestFileSystem |
| 21 from test_util import EnableLogging | 22 from test_util import EnableLogging |
| 22 | 23 |
| 23 | 24 |
| 24 def _GenerateFakeHash(): | 25 class _TestBundle(object): |
| 25 '''Generates a fake SHA1 hash. | 26 '''Bundles test file data with a GithubFileSystem and test utilites. Create |
| 27 GithubFileSystems via |CreateGfs()|, the Fetcher it uses as |fetcher|, |
| 28 randomly mutate its contents via |Mutate()|, and access the underlying zip |
| 29 data via |files|. |
| 26 ''' | 30 ''' |
| 27 return sha1(str(random())).hexdigest() | 31 |
| 32 def __init__(self): |
| 33 self.files = { |
| 34 'zipfile/': '', |
| 35 'zipfile/hello.txt': 'world', |
| 36 'zipfile/readme': 'test zip', |
| 37 'zipfile/dir/file1': 'contents', |
| 38 'zipfile/dir/file2': 'more contents' |
| 39 } |
| 40 self._test_files = { |
| 41 'test_owner': { |
| 42 'changing-repo': { |
| 43 'commits': { |
| 44 'HEAD': self._MakeShaJson(self._GenerateHash()) |
| 45 }, |
| 46 'zipball': self._ZipFromFiles(self.files) |
| 47 } |
| 48 } |
| 49 } |
| 28 | 50 |
| 29 | 51 |
| 30 def _ZipFromFiles(file_dict): | 52 def CreateGfsAndFetcher(self): |
| 31 string = StringIO() | 53 fetchers = [] |
| 32 zipfile = ZipFile(string, 'w') | 54 def create_mock_url_fetcher(base_path): |
| 33 for filename, contents in file_dict.iteritems(): | 55 assert not fetchers |
| 34 zipfile.writestr(filename, contents) | 56 fetchers.append(MockURLFetcher( |
| 35 zipfile.close() | 57 FakeURLFSFetcher(TestFileSystem(self._test_files), base_path))) |
| 58 return fetchers[-1] |
| 36 | 59 |
| 37 return string.getvalue() | 60 # Constructing |gfs| will create a fetcher. |
| 61 gfs = GithubFileSystem.ForTest( |
| 62 'changing-repo', create_mock_url_fetcher, path='') |
| 63 assert len(fetchers) == 1 |
| 64 return gfs, fetchers[0] |
| 65 |
| 66 def Mutate(self): |
| 67 fake_version = self._GenerateHash() |
| 68 fake_data = self._GenerateHash() |
| 69 self.files['zipfile/hello.txt'] = fake_data |
| 70 self.files['zipfile/new-file'] = fake_data |
| 71 self.files['zipfile/dir/file1'] = fake_data |
| 72 self._test_files['test_owner']['changing-repo']['zipball'] = ( |
| 73 self._ZipFromFiles(self.files)) |
| 74 self._test_files['test_owner']['changing-repo']['commits']['HEAD'] = ( |
| 75 self._MakeShaJson(fake_version)) |
| 76 return fake_version, fake_data |
| 77 |
| 78 def _GenerateHash(self): |
| 79 '''Generates an arbitrary SHA1 hash. |
| 80 ''' |
| 81 return sha1(str(random())).hexdigest() |
| 82 |
| 83 def _MakeShaJson(self, hash_value): |
| 84 commit_json = json.loads(deepcopy(LocalFileSystem('').ReadSingle( |
| 85 'test_data/github_file_system/test_owner/repo/commits/HEAD').Get())) |
| 86 commit_json['sha'] = hash_value |
| 87 return json.dumps(commit_json) |
| 88 |
| 89 def _ZipFromFiles(self, file_dict): |
| 90 string = StringIO() |
| 91 zipfile = ZipFile(string, 'w') |
| 92 for filename, contents in file_dict.iteritems(): |
| 93 zipfile.writestr(filename, contents) |
| 94 zipfile.close() |
| 95 return string.getvalue() |
| 38 | 96 |
| 39 | 97 |
| 40 class TestGithubFileSystem(unittest.TestCase): | 98 class TestGithubFileSystem(unittest.TestCase): |
| 41 def setUp(self): | 99 def setUp(self): |
| 42 self._gfs = GithubFileSystem.ForTest( | 100 self._gfs = GithubFileSystem.ForTest( |
| 43 'repo', partial(FakeURLFSFetcher, LocalFileSystem(''))) | 101 'repo', partial(FakeURLFSFetcher, LocalFileSystem(''))) |
| 44 # Start and finish the repository load. | 102 # Start and finish the repository load. |
| 45 self._cgfs = CachingFileSystem(self._gfs, ObjectStoreCreator.ForTest()) | 103 self._cgfs = CachingFileSystem(self._gfs, ObjectStoreCreator.ForTest()) |
| 46 | 104 |
| 47 def testReadDirectory(self): | 105 def testReadDirectory(self): |
| (...skipping 25 matching lines...) Expand all Loading... |
| 73 'src/': sorted(['hello.notpy', '__init__.notpy']), | 131 'src/': sorted(['hello.notpy', '__init__.notpy']), |
| 74 '': sorted(['requirements.txt', '.gitignore', 'README.md', 'src/']) | 132 '': sorted(['requirements.txt', '.gitignore', 'README.md', 'src/']) |
| 75 } | 133 } |
| 76 | 134 |
| 77 read = self._gfs.Read(['', 'src/']).Get() | 135 read = self._gfs.Read(['', 'src/']).Get() |
| 78 self.assertEqual(expected['src/'], sorted(read['src/'])) | 136 self.assertEqual(expected['src/'], sorted(read['src/'])) |
| 79 self.assertEqual(expected[''], sorted(read[''])) | 137 self.assertEqual(expected[''], sorted(read[''])) |
| 80 | 138 |
| 81 def testStat(self): | 139 def testStat(self): |
| 82 # This is the hash value from the zip on disk. | 140 # This is the hash value from the zip on disk. |
| 83 real_hash = '7becb9f554dec76bd0fc12c1d32dbaff1d134a4d' | 141 real_hash = 'c36fc23688a9ec9e264d3182905dc0151bfff7d7' |
| 84 | 142 |
| 85 self._gfs.Refresh().Get() | 143 self._gfs.Refresh().Get() |
| 86 dir_stat = StatInfo(real_hash, { | 144 dir_stat = StatInfo(real_hash, { |
| 87 'hello.notpy': StatInfo(real_hash), | 145 'hello.notpy': StatInfo(real_hash), |
| 88 '__init__.notpy': StatInfo(real_hash) | 146 '__init__.notpy': StatInfo(real_hash) |
| 89 }) | 147 }) |
| 90 | 148 |
| 91 self.assertEqual(StatInfo(real_hash), self._gfs.Stat('README.md')) | 149 self.assertEqual(StatInfo(real_hash), self._gfs.Stat('README.md')) |
| 92 self.assertEqual(StatInfo(real_hash), self._gfs.Stat('src/hello.notpy')) | 150 self.assertEqual(StatInfo(real_hash), self._gfs.Stat('src/hello.notpy')) |
| 93 self.assertEqual(dir_stat, self._gfs.Stat('src/')) | 151 self.assertEqual(dir_stat, self._gfs.Stat('src/')) |
| (...skipping 23 matching lines...) Expand all Loading... |
| 117 initial_cgfs_read_two, | 175 initial_cgfs_read_two, |
| 118 self._cgfs.Read(['README.md', 'requirements.txt']).Get()) | 176 self._cgfs.Read(['README.md', 'requirements.txt']).Get()) |
| 119 | 177 |
| 120 def testWithoutRefresh(self): | 178 def testWithoutRefresh(self): |
| 121 # Without refreshing it will still read the content from blobstore, and it | 179 # Without refreshing it will still read the content from blobstore, and it |
| 122 # does this via the magic of the FakeURLFSFetcher. | 180 # does this via the magic of the FakeURLFSFetcher. |
| 123 self.assertEqual(['__init__.notpy', 'hello.notpy'], | 181 self.assertEqual(['__init__.notpy', 'hello.notpy'], |
| 124 sorted(self._gfs.ReadSingle('src/').Get())) | 182 sorted(self._gfs.ReadSingle('src/').Get())) |
| 125 | 183 |
| 126 def testRefresh(self): | 184 def testRefresh(self): |
| 127 def make_sha_json(hash_value): | 185 test_bundle = _TestBundle() |
| 128 from copy import deepcopy | 186 gfs, fetcher = test_bundle.CreateGfsAndFetcher() |
| 129 commit_json = json.loads(deepcopy(LocalFileSystem('').ReadSingle( | |
| 130 'test_data/github_file_system/test_owner/repo/commits/HEAD').Get())) | |
| 131 commit_json['commit']['tree']['sha'] = hash_value | |
| 132 return json.dumps(commit_json) | |
| 133 | |
| 134 files = { | |
| 135 'zipfile/': '', | |
| 136 'zipfile/hello.txt': 'world', | |
| 137 'zipfile/readme': 'test zip', | |
| 138 'zipfile/dir/file1': 'contents', | |
| 139 'zipfile/dir/file2': 'more contents' | |
| 140 } | |
| 141 | |
| 142 string = _ZipFromFiles(files) | |
| 143 | |
| 144 test_files = { | |
| 145 'test_owner': { | |
| 146 'changing-repo': { | |
| 147 'commits': { | |
| 148 'HEAD': make_sha_json(_GenerateFakeHash()) | |
| 149 }, | |
| 150 'zipball': string | |
| 151 } | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 def mutate_file_data(): | |
| 156 fake_hash = _GenerateFakeHash() | |
| 157 files['zipfile/hello.txt'] = fake_hash | |
| 158 files['zipfile/new-file'] = fake_hash | |
| 159 files['zipfile/dir/file1'] = fake_hash | |
| 160 test_files['test_owner']['changing-repo']['zipball'] = _ZipFromFiles( | |
| 161 files) | |
| 162 test_files['test_owner']['changing-repo']['commits']['HEAD'] = ( | |
| 163 make_sha_json(fake_hash)) | |
| 164 return fake_hash, fake_hash | |
| 165 | |
| 166 test_file_system = TestFileSystem(test_files) | |
| 167 fetchers = [] | |
| 168 def create_mock_url_fetcher(base_path): | |
| 169 fetchers.append( | |
| 170 MockURLFetcher(FakeURLFSFetcher(test_file_system, base_path))) | |
| 171 return fetchers[-1] | |
| 172 gfs = GithubFileSystem.ForTest( | |
| 173 'changing-repo', create_mock_url_fetcher, path='') | |
| 174 fetcher = fetchers[0] | |
| 175 | 187 |
| 176 # It shouldn't fetch until Refresh does so; then it will do 2, one for the | 188 # It shouldn't fetch until Refresh does so; then it will do 2, one for the |
| 177 # stat, and another for the read. | 189 # stat, and another for the read. |
| 178 self.assertTrue(*fetcher.CheckAndReset()) | 190 self.assertTrue(*fetcher.CheckAndReset()) |
| 179 gfs.Refresh().Get() | 191 gfs.Refresh().Get() |
| 180 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, | 192 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, |
| 181 fetch_async_count=1, | 193 fetch_async_count=1, |
| 182 fetch_resolve_count=1)) | 194 fetch_resolve_count=1)) |
| 183 | 195 |
| 184 # Refreshing again will stat but not fetch. | 196 # Refresh is just an alias for Read(''). |
| 185 gfs.Refresh().Get() | 197 gfs.Refresh().Get() |
| 186 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1)) | 198 self.assertTrue(*fetcher.CheckAndReset()) |
| 187 | 199 |
| 188 initial_dir_read = sorted(gfs.ReadSingle('').Get()) | 200 initial_dir_read = sorted(gfs.ReadSingle('').Get()) |
| 189 initial_file_read = gfs.ReadSingle('dir/file1').Get() | 201 initial_file_read = gfs.ReadSingle('dir/file1').Get() |
| 190 | 202 |
| 191 version, data = mutate_file_data() | 203 version, data = test_bundle.Mutate() |
| 192 | 204 |
| 193 # Check that changes have not effected the file system yet. | 205 # Check that changes have not effected the file system yet. |
| 194 self.assertEqual(initial_dir_read, sorted(gfs.ReadSingle('').Get())) | 206 self.assertEqual(initial_dir_read, sorted(gfs.ReadSingle('').Get())) |
| 195 self.assertEqual(initial_file_read, gfs.ReadSingle('dir/file1').Get()) | 207 self.assertEqual(initial_file_read, gfs.ReadSingle('dir/file1').Get()) |
| 196 self.assertNotEqual(StatInfo(version), gfs.Stat('')) | 208 self.assertNotEqual(StatInfo(version), gfs.Stat('')) |
| 197 | 209 |
| 210 gfs, fetcher = test_bundle.CreateGfsAndFetcher() |
| 198 gfs.Refresh().Get() | 211 gfs.Refresh().Get() |
| 199 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, | 212 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, |
| 200 fetch_async_count=1, | 213 fetch_async_count=1, |
| 201 fetch_resolve_count=1)) | 214 fetch_resolve_count=1)) |
| 202 | 215 |
| 203 # Check that the changes have affected the file system. | 216 # Check that the changes have affected the file system. |
| 204 self.assertEqual(data, gfs.ReadSingle('new-file').Get()) | 217 self.assertEqual(data, gfs.ReadSingle('new-file').Get()) |
| 205 self.assertEqual(files['zipfile/dir/file1'], | 218 self.assertEqual(test_bundle.files['zipfile/dir/file1'], |
| 206 gfs.ReadSingle('dir/file1').Get()) | 219 gfs.ReadSingle('dir/file1').Get()) |
| 207 self.assertEqual(StatInfo(version), gfs.Stat('new-file')) | 220 self.assertEqual(StatInfo(version), gfs.Stat('new-file')) |
| 208 | 221 |
| 209 # Regression test: ensure that reading the data after it's been mutated, | 222 # Regression test: ensure that reading the data after it's been mutated, |
| 210 # but before Refresh() has been realised, still returns the correct data. | 223 # but before Refresh() has been realised, still returns the correct data. |
| 211 version, data = mutate_file_data() | 224 gfs, fetcher = test_bundle.CreateGfsAndFetcher() |
| 225 version, data = test_bundle.Mutate() |
| 212 | 226 |
| 213 refresh_future = gfs.Refresh() | 227 refresh_future = gfs.Refresh() |
| 214 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, fetch_async_count=1)) | 228 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, fetch_async_count=1)) |
| 215 | 229 |
| 216 self.assertEqual(data, gfs.ReadSingle('new-file').Get()) | 230 self.assertEqual(data, gfs.ReadSingle('new-file').Get()) |
| 217 self.assertEqual(files['zipfile/dir/file1'], | 231 self.assertEqual(test_bundle.files['zipfile/dir/file1'], |
| 218 gfs.ReadSingle('dir/file1').Get()) | 232 gfs.ReadSingle('dir/file1').Get()) |
| 219 self.assertEqual(StatInfo(version), gfs.Stat('new-file')) | 233 self.assertEqual(StatInfo(version), gfs.Stat('new-file')) |
| 220 | 234 |
| 221 refresh_future.Get() | 235 refresh_future.Get() |
| 222 self.assertTrue(*fetcher.CheckAndReset(fetch_resolve_count=1)) | 236 self.assertTrue(*fetcher.CheckAndReset(fetch_resolve_count=1)) |
| 223 | 237 |
| 238 def testGetThenRefreshOnStartup(self): |
| 239 # Regression test: Test that calling Get() but never resolving the future, |
| 240 # then Refresh()ing the data, causes the data to be refreshed. |
| 241 test_bundle = _TestBundle() |
| 242 gfs, fetcher = test_bundle.CreateGfsAndFetcher() |
| 243 self.assertTrue(*fetcher.CheckAndReset()) |
| 244 |
| 245 # Get a predictable version. |
| 246 version, data = test_bundle.Mutate() |
| 247 |
| 248 read_future = gfs.ReadSingle('hello.txt') |
| 249 # Fetch for the Stat(), async-fetch for the Read(). |
| 250 self.assertTrue(*fetcher.CheckAndReset(fetch_count=1, fetch_async_count=1)) |
| 251 |
| 252 refresh_future = gfs.Refresh() |
| 253 self.assertTrue(*fetcher.CheckAndReset()) |
| 254 |
| 255 self.assertEqual(data, read_future.Get()) |
| 256 self.assertTrue(*fetcher.CheckAndReset(fetch_resolve_count=1)) |
| 257 self.assertEqual(StatInfo(version), gfs.Stat('hello.txt')) |
| 258 self.assertTrue(*fetcher.CheckAndReset()) |
| 259 |
| 260 # The fetch will already have been resolved, so resolving the Refresh won't |
| 261 # affect anything. |
| 262 refresh_future.Get() |
| 263 self.assertTrue(*fetcher.CheckAndReset()) |
| 264 |
| 265 # Read data should not have changed. |
| 266 self.assertEqual(data, gfs.ReadSingle('hello.txt').Get()) |
| 267 self.assertEqual(StatInfo(version), gfs.Stat('hello.txt')) |
| 268 self.assertTrue(*fetcher.CheckAndReset()) |
| 269 |
| 224 | 270 |
| 225 if __name__ == '__main__': | 271 if __name__ == '__main__': |
| 226 unittest.main() | 272 unittest.main() |
| OLD | NEW |