diff options
8 files changed, 96 insertions, 41 deletions
diff --git a/chrome/common/extensions/docs/server2/caching_file_system.py b/chrome/common/extensions/docs/server2/caching_file_system.py index 59d8030..c0ff5d0 100644 --- a/chrome/common/extensions/docs/server2/caching_file_system.py +++ b/chrome/common/extensions/docs/server2/caching_file_system.py @@ -63,14 +63,12 @@ class CachingFileSystem(FileSystem): if dir_stat is not None: return Future(value=make_stat_info(dir_stat)) - dir_stat_future = self._MemoizedStatAsyncFromFileSystem(dir_path) - def resolve(): - dir_stat = dir_stat_future.Get() + def next(dir_stat): assert dir_stat is not None # should have raised a FileNotFoundError # We only ever need to cache the dir stat. self._stat_object_store.Set(dir_path, dir_stat) return make_stat_info(dir_stat) - return Future(callback=resolve) + return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next) @memoize def _MemoizedStatAsyncFromFileSystem(self, dir_path): @@ -93,18 +91,17 @@ class CachingFileSystem(FileSystem): # with a value. stat_futures = {} - def swallow_file_not_found_error(future): - def resolve(): - try: return future.Get() - except FileNotFoundError: return Nnone - return Future(callback=resolve) + def handle(error): + if isinstance(error, FileNotFoundError): + return None + raise error for path in paths: stat_value = cached_stat_values.get(path) if stat_value is None: stat_future = self.StatAsync(path) if skip_not_found: - stat_future = swallow_file_not_found_error(stat_future) + stat_future = stat_future.Then(lambda x: x, handle) else: stat_future = Future(value=stat_value) stat_futures[path] = stat_future @@ -120,19 +117,16 @@ class CachingFileSystem(FileSystem): # Everything was cached and up-to-date. return Future(value=fresh_data) - # Read in the values that were uncached or old. - read_futures = self._file_system.Read( - set(paths) - set(fresh_data.iterkeys()), - skip_not_found=skip_not_found) - def resolve(): - new_results = read_futures.Get() + def next(new_results): # Update the cache. This is a path -> (data, version) mapping. self._read_object_store.SetMulti( dict((path, (new_result, stat_futures[path].Get().version)) for path, new_result in new_results.iteritems())) new_results.update(fresh_data) return new_results - return Future(callback=resolve) + # Read in the values that were uncached or old. + return self._file_system.Read(set(paths) - set(fresh_data.iterkeys()), + skip_not_found=skip_not_found).Then(next) def GetIdentity(self): return self._file_system.GetIdentity() diff --git a/chrome/common/extensions/docs/server2/chroot_file_system.py b/chrome/common/extensions/docs/server2/chroot_file_system.py index af521d8..2ed612f 100644 --- a/chrome/common/extensions/docs/server2/chroot_file_system.py +++ b/chrome/common/extensions/docs/server2/chroot_file_system.py @@ -32,13 +32,11 @@ class ChrootFileSystem(FileSystem): prefixed = posixpath.join(self._root, path) prefixed_paths[prefixed] = path return prefixed - future_result = self._file_system.Read( - tuple(prefix(path) for path in paths), - skip_not_found=skip_not_found) - def resolve(): + def next(results): return dict((prefixed_paths[path], content) - for path, content in future_result.Get().iteritems()) - return Future(callback=resolve) + for path, content in results.iteritems()) + return self._file_system.Read(tuple(prefix(path) for path in paths), + skip_not_found-skip_not_found).Then(next) def Refresh(self): return self._file_system.Refresh() diff --git a/chrome/common/extensions/docs/server2/fake_url_fetcher.py b/chrome/common/extensions/docs/server2/fake_url_fetcher.py index dbfaa55..21b6cf6 100644 --- a/chrome/common/extensions/docs/server2/fake_url_fetcher.py +++ b/chrome/common/extensions/docs/server2/fake_url_fetcher.py @@ -129,11 +129,10 @@ class MockURLFetcher(object): def FetchAsync(self, url, **kwargs): self._fetch_async_count += 1 - future = self._fetcher.FetchAsync(url, **kwargs) - def resolve(): + def next(result): self._fetch_resolve_count += 1 - return future.Get() - return Future(callback=resolve) + return result + return self._fetcher.FetchAsync(url, **kwargs).Then(next) def CheckAndReset(self, fetch_count=0, diff --git a/chrome/common/extensions/docs/server2/file_system.py b/chrome/common/extensions/docs/server2/file_system.py index 99e33c5..1e5b567 100644 --- a/chrome/common/extensions/docs/server2/file_system.py +++ b/chrome/common/extensions/docs/server2/file_system.py @@ -106,13 +106,12 @@ class FileSystem(object): return Future(value=True) parent, base = SplitParent(path) - list_future = self.ReadSingle(ToDirectory(parent)) - def resolve(): - try: - return base in list_future.Get() - except FileNotFoundError: + def handle(error): + if isinstance(error, FileNotFoundError): return False - return Future(callback=resolve) + raise error + return self.ReadSingle(ToDirectory(parent)).Then(lambda l: base in l, + handle) def Refresh(self): '''Asynchronously refreshes the content of the FileSystem, returning a diff --git a/chrome/common/extensions/docs/server2/future.py b/chrome/common/extensions/docs/server2/future.py index 4b09073..51c2842 100644 --- a/chrome/common/extensions/docs/server2/future.py +++ b/chrome/common/extensions/docs/server2/future.py @@ -7,6 +7,10 @@ import sys _no_value = object() +def _DefaultErrorHandler(error): + raise error + + def All(futures, except_pass=None): '''Creates a Future which returns a list of results from each Future in |futures|. @@ -61,12 +65,17 @@ class Future(object): self._exc_info is None): raise ValueError('Must have either a value, error, or callback.') - def Then(self, callback): + def Then(self, callback, error_handler=_DefaultErrorHandler): '''Creates and returns a future that runs |callback| on the value of this - future. + future, or runs optional |error_handler| if resolving this future results in + an exception. ''' def then(): - return callback(self.Get()) + try: + val = self.Get() + except Exception as e: + return error_handler(e) + return callback(val) return Future(callback=then) def Get(self): diff --git a/chrome/common/extensions/docs/server2/future_test.py b/chrome/common/extensions/docs/server2/future_test.py index 440994b..1c188d1 100755 --- a/chrome/common/extensions/docs/server2/future_test.py +++ b/chrome/common/extensions/docs/server2/future_test.py @@ -159,6 +159,63 @@ class FutureTest(unittest.TestCase): except_pass=(ValueError,)) self.assertRaises(ValueError, race.Get) + def testThen(self): + def assertIs42(val): + self.assertEquals(val, 42) + + then = Future(value=42).Then(assertIs42) + # Shouldn't raise an error. + then.Get() + + # Test raising an error. + then = Future(value=41).Then(assertIs42) + self.assertRaises(AssertionError, then.Get) + + # Test setting up an error handler. + def handle(error): + if isinstance(error, ValueError): + return 'Caught' + raise error + + def raiseValueError(): + raise ValueError + + def raiseException(): + raise Exception + + then = Future(callback=raiseValueError).Then(assertIs42, handle) + self.assertEquals(then.Get(), 'Caught') + then = Future(callback=raiseException).Then(assertIs42, handle) + self.assertRaises(Exception, then.Get) + + # Test chains of thens. + addOne = lambda val: val + 1 + then = Future(value=40).Then(addOne).Then(addOne).Then(assertIs42) + # Shouldn't raise an error. + then.Get() + + # Test error in chain. + then = Future(value=40).Then(addOne).Then(assertIs42).Then(addOne) + self.assertRaises(AssertionError, then.Get) + + # Test handle error in chain. + def raiseValueErrorWithVal(val): + raise ValueError + + then = Future(value=40).Then(addOne).Then(raiseValueErrorWithVal).Then( + addOne, handle).Then(lambda val: val + ' me') + self.assertEquals(then.Get(), 'Caught me') + + # Test multiple handlers. + def myHandle(error): + if isinstance(error, AssertionError): + return 10 + raise error + + then = Future(value=40).Then(assertIs42).Then(addOne, handle).Then(addOne, + myHandle) + self.assertEquals(then.Get(), 10) + if __name__ == '__main__': unittest.main() diff --git a/chrome/common/extensions/docs/server2/mock_file_system.py b/chrome/common/extensions/docs/server2/mock_file_system.py index e15f58c..52a84bd 100644 --- a/chrome/common/extensions/docs/server2/mock_file_system.py +++ b/chrome/common/extensions/docs/server2/mock_file_system.py @@ -42,16 +42,15 @@ class MockFileSystem(FileSystem): from |_updates|, if any. ''' self._read_count += 1 - future_result = self._file_system.Read(paths, skip_not_found=skip_not_found) - def resolve(): + def next(result): self._read_resolve_count += 1 - result = future_result.Get() for path in result.iterkeys(): update = self._GetMostRecentUpdate(path) if update is not None: result[path] = update return result - return Future(callback=resolve) + return self._file_system.Read(paths, + skip_not_found=skip_not_found).Then(next) def Refresh(self): return self._file_system.Refresh() diff --git a/chrome/common/extensions/docs/server2/mock_file_system_test.py b/chrome/common/extensions/docs/server2/mock_file_system_test.py index f2fe7d4..813751f 100755 --- a/chrome/common/extensions/docs/server2/mock_file_system_test.py +++ b/chrome/common/extensions/docs/server2/mock_file_system_test.py @@ -60,7 +60,7 @@ class MockFileSystemTest(unittest.TestCase): future = fs.Read(['notfound.html', 'apps/']) self.assertTrue(*fs.CheckAndReset(read_count=1)) self.assertRaises(FileNotFoundError, future.Get) - self.assertTrue(*fs.CheckAndReset(read_resolve_count=1)) + self.assertTrue(*fs.CheckAndReset(read_resolve_count=0)) fs.Stat('404.html') fs.Stat('404.html') |