From 40ce6ee2b8c703a5d5f3fdfa4f2036abf091544c Mon Sep 17 00:00:00 2001 From: aiolos Date: Mon, 31 Aug 2015 17:04:20 -0700 Subject: [PATCH] Add DependencyInfo and DepedencyManager exceptions. BUG=510231 Review URL: https://codereview.chromium.org/1321233002 Cr-Commit-Position: refs/heads/master@{#346528} --- .../catapult_base/dependency_manager/__init__.py | 32 +++++ .../dependency_manager/dependency_info.py | 130 +++++++++++++++++++ .../dependency_manager/dependency_info_unittest.py | 138 +++++++++++++++++++++ ...ncy_manager.py => dependency_manager_module.py} | 0 tools/telemetry/telemetry/core/exceptions.py | 2 + 5 files changed, 302 insertions(+) create mode 100644 tools/telemetry/catapult_base/dependency_manager/__init__.py create mode 100644 tools/telemetry/catapult_base/dependency_manager/dependency_info.py create mode 100644 tools/telemetry/catapult_base/dependency_manager/dependency_info_unittest.py rename tools/telemetry/catapult_base/{dependency_manager.py => dependency_manager_module.py} (100%) diff --git a/tools/telemetry/catapult_base/dependency_manager/__init__.py b/tools/telemetry/catapult_base/dependency_manager/__init__.py new file mode 100644 index 000000000000..687b9423d027 --- /dev/null +++ b/tools/telemetry/catapult_base/dependency_manager/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from catapult_base.dependency_manager.dependency_info import DependencyInfo +from catapult_base.dependency_manager_module import DependencyManager + +class UnsupportedConfigFormatError(ValueError): + def __init__(self, config_type, config_file): + if not config_type: + message = ('The json file at %s is unsupported by the dependency_manager ' + 'due to no specified config type' % config_file) + else: + message = ('The json file at %s has config type %s, which is unsupported ' + 'by the dependency manager.' % (config_file, config_type)) + super(UnsupportedConfigFormatError, self).__init__(message) + +class EmptyConfigError(ValueError): + def __init__(self, file_path): + super(EmptyConfigError, self).__init__('Empty config at %s.' % file_path) + + +class FileNotFoundError(Exception): + def __init__(self, file_path): + super(FileNotFoundError, self).__init__('No file found at %s' % file_path) + + +class NoPathFoundError(FileNotFoundError): + def __init__(self, dependency, platform): + super(NoPathFoundError, self).__init__( + 'No file could be found locally, and no file to download from cloud ' + 'storage for %s on platform %s' % (dependency, platform)) diff --git a/tools/telemetry/catapult_base/dependency_manager/dependency_info.py b/tools/telemetry/catapult_base/dependency_manager/dependency_info.py new file mode 100644 index 000000000000..f5138daa17ce --- /dev/null +++ b/tools/telemetry/catapult_base/dependency_manager/dependency_info.py @@ -0,0 +1,130 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +class DependencyInfo(object): + def __init__(self, dependency, platform, config_file, cs_bucket=None, + cs_hash=None, download_path=None, cs_remote_path=None, + local_paths=None): + """ Container for the information needed for each dependency/platform pair + in the dependency_manager. + + Information about the file: + dependency: Name of the dependency. + platform: Name of the platform to be run on. + config_file: Path to the config_file this information came from. Used for + error messages to improve debugging. + + Information used for downloading from cloud storage: + cs_bucket: The cloud_storage bucket the dependency is located in. + cs_hash: The hash of the file stored in cloud_storage. + download_path: Where the file should be downloaded to. + cs_remote_path: Where the file is stored in the cloud_storage bucket. + + local_paths: A list of paths to search in order for a local file. + """ + # TODO(aiolos): update the above doc string for A) the usage of zip files + # and B) supporting lists of local_paths to be checked for most recently + # changed files. + if not dependency or not platform: + raise ValueError( + 'Must supply both a dependency and platform to DependencyInfo') + + self._dependency = dependency + self._platform = platform + self._config_files = [config_file] + self._local_paths = local_paths or [] + self._download_path = download_path + self._cs_remote_path = cs_remote_path + self._cs_bucket = cs_bucket + self._cs_hash = cs_hash + self.VerifyCloudStorageInfo() + + def Update(self, new_dep_info, append_to_front): + """Add the information from |new_dep_info| to this instance. + + append_to_front: Whether new local_paths should be appended to the front of + the local_paths list, or the end. + """ + self._config_files.extend(new_dep_info.config_files) + if (self.dependency != new_dep_info.dependency or + self.platform != new_dep_info.platform): + raise ValueError( + 'Cannot update DependencyInfo with different dependency or platform.' + 'Existing dep: %s, existing platform: %s. New dep: %s, new platform:' + '%s. Config_files conflicting: %s' % ( + self.dependency, self.platform, new_dep_info.dependency, + new_dep_info.platform, self.config_files)) + if new_dep_info.has_cs_info: + if self.has_cs_info: + raise ValueError( + 'Overriding cloud_storage data is not allowed when updating a ' + 'DependencyInfo. Conflict in dependency %s on platform %s in ' + 'config_files: %s.' % (self.dependency, self.platform, + self.config_files)) + else: + self._download_path = new_dep_info.download_path + self._cs_remote_path = new_dep_info.cs_remote_path + self._cs_bucket = new_dep_info.cs_bucket + self._cs_hash = new_dep_info.cs_hash + if new_dep_info.local_paths: + if append_to_front: + self._local_paths = [path for path in self._local_paths if + path not in new_dep_info.local_paths] + self._local_paths[0:0] = new_dep_info.local_paths + else: + for path in new_dep_info.local_paths: + if path not in self._local_paths: + self._local_paths.append(path) + + @property + def dependency(self): + return self._dependency + + @property + def platform(self): + return self._platform + + @property + def config_files(self): + return self._config_files + + @property + def local_paths(self): + return self._local_paths + + @property + def download_path(self): + return self._download_path + + @property + def cs_remote_path(self): + return self._cs_remote_path + + @property + def cs_bucket(self): + return self._cs_bucket + + @property + def cs_hash(self): + return self._cs_hash + + @property + def has_cs_info(self): + self.VerifyCloudStorageInfo() + return self.cs_hash + + def VerifyCloudStorageInfo(self): + """Ensure either all or none of the needed remote information is specified. + """ + if ((self.cs_bucket or self.cs_remote_path or self.download_path or + self.cs_hash) and not (self.cs_bucket and self.cs_remote_path and + self.download_path and self.cs_hash)): + raise ValueError( + 'Attempted to partially initialize cloud storage data for ' + 'dependency: %s, platform: %s, download_path: %s, ' + 'cs_remote_path: %s, cs_bucket %s, cs_hash %s' % ( + self.dependency, self.platform, self.download_path, + self.cs_remote_path, self.cs_bucket, self.cs_hash)) + diff --git a/tools/telemetry/catapult_base/dependency_manager/dependency_info_unittest.py b/tools/telemetry/catapult_base/dependency_manager/dependency_info_unittest.py new file mode 100644 index 000000000000..6e307c25f178 --- /dev/null +++ b/tools/telemetry/catapult_base/dependency_manager/dependency_info_unittest.py @@ -0,0 +1,138 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +from catapult_base.dependency_manager import dependency_info + +class DependencyInfoTest(unittest.TestCase): + def testInitErrors(self): + # Must have a dependency, platform and file_path. + self.assertRaises(ValueError, dependency_info.DependencyInfo, + None, None, None) + self.assertRaises(ValueError, dependency_info.DependencyInfo, + 'dep', None, None) + self.assertRaises(ValueError, dependency_info.DependencyInfo, + None, 'plat', None) + self.assertRaises(ValueError, dependency_info.DependencyInfo, + None, None, 'config_file') + # Must specify cloud storage information atomically. + self.assertRaises(ValueError, dependency_info.DependencyInfo, 'dep', 'plat', + 'config_file', cs_bucket='cs_b') + self.assertRaises(ValueError, dependency_info.DependencyInfo, 'dep', 'plat', + 'config_file', cs_hash='cs_hash') + self.assertRaises(ValueError, dependency_info.DependencyInfo, 'dep', 'plat', + 'config_file', cs_bucket='cs_bucket', cs_hash='cs_hash', + cs_remote_path='cs_remote_path', local_paths=['path2']) + + def testInitEmpty(self): + empty_di = dependency_info.DependencyInfo('dep', 'plat', 'config_file') + self.assertFalse(empty_di.cs_bucket or empty_di.cs_hash or + empty_di.download_path or empty_di.cs_remote_path or + empty_di.local_paths) + self.assertEqual('dep', empty_di.dependency) + self.assertEqual('plat', empty_di.platform) + self.assertEqual(['config_file'], empty_di.config_files) + + def testUpdateRequiredArgsConflicts(self): + dep_info1 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file1', local_paths=['path0', 'path1']) + dep_info2 = dependency_info.DependencyInfo( + 'dep1', 'platform2', 'config_file2', local_paths=['path0', 'path2']) + dep_info3 = dependency_info.DependencyInfo( + 'dep2', 'platform1', 'config_file3', local_paths=['path0', 'path3']) + self.assertRaises(ValueError, dep_info1.Update, dep_info2, False) + self.assertRaises(ValueError, dep_info1.Update, dep_info3, False) + self.assertRaises(ValueError, dep_info3.Update, dep_info2, False) + self.assertRaises(ValueError, dep_info1.Update, dep_info2, True) + self.assertRaises(ValueError, dep_info1.Update, dep_info3, True) + self.assertRaises(ValueError, dep_info3.Update, dep_info2, True) + + def testUpdateCloudStorageInfo(self): + dep_info1 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file1') + dep_info2 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file2', cs_bucket='cs_bucket', + cs_hash='cs_hash', download_path='download_path', + cs_remote_path='cs_remote_path') + dep_info3 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file3') + dep_info4 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file4', cs_bucket='cs_bucket', + cs_hash='cs_hash', download_path='download_path', + cs_remote_path='cs_remote_path') + + dep_info1.Update(dep_info2, False) + self.assertEqual('cs_bucket', dep_info1.cs_bucket) + self.assertEqual('cs_hash', dep_info1.cs_hash) + self.assertEqual('download_path', dep_info1.download_path) + self.assertEqual('cs_remote_path', dep_info1.cs_remote_path) + self.assertFalse(dep_info1.local_paths) + + dep_info1.Update(dep_info3, False) + self.assertEqual('cs_bucket', dep_info1.cs_bucket) + self.assertEqual('cs_hash', dep_info1.cs_hash) + self.assertEqual('download_path', dep_info1.download_path) + self.assertEqual('cs_remote_path', dep_info1.cs_remote_path) + self.assertFalse(dep_info1.local_paths) + + self.assertRaises(ValueError, dep_info1.Update, dep_info4, False) + + def testUpdateAllInfo(self): + dep_info1 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file1', local_paths=['path1']) + dep_info2 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file2', cs_bucket='cs_bucket', + cs_hash='cs_hash', download_path='download_path', + cs_remote_path='cs_remote_path', local_paths=['path2']) + dep_info3 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file3', local_paths=['path3']) + dep_info4 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file4', cs_bucket='cs_bucket', + cs_hash='cs_hash', download_path='download_path', + cs_remote_path='cs_remote_path', local_paths=['path4']) + + dep_info1.Update(dep_info2, False) + self.assertEqual('cs_bucket', dep_info1.cs_bucket) + self.assertEqual('cs_hash', dep_info1.cs_hash) + self.assertEqual('download_path', dep_info1.download_path) + self.assertEqual('cs_remote_path', dep_info1.cs_remote_path) + self.assertEqual(['path1', 'path2'], dep_info1.local_paths) + + dep_info1.Update(dep_info3, False) + self.assertEqual('cs_bucket', dep_info1.cs_bucket) + self.assertEqual('cs_hash', dep_info1.cs_hash) + self.assertEqual('download_path', dep_info1.download_path) + self.assertEqual('cs_remote_path', dep_info1.cs_remote_path) + self.assertEqual(['path1', 'path2', 'path3'], dep_info1.local_paths) + + self.assertRaises(ValueError, dep_info1.Update, dep_info4, False) + + + def testAppendToFront(self): + dep_info1 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file1', + local_paths=['path0', 'path1', 'path3', 'path5', 'path6']) + dep_info2 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file2', + local_paths=['path0', 'path2', 'path4', 'path5']) + + expected_local_paths = ['path0', 'path2', 'path4', 'path5', 'path1', + 'path3', 'path6'] + dep_info1.Update(dep_info2, True) + self.assertEquals(expected_local_paths, dep_info1.local_paths) + + def testAppendToEnd(self): + dep_info1 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file1', + local_paths=['path0', 'path1', 'path3', 'path5', 'path6']) + dep_info2 = dependency_info.DependencyInfo( + 'dep1', 'platform1', 'config_file2', + local_paths=['path0', 'path2', 'path4', 'path5']) + + expected_local_paths = ['path0', 'path1', 'path3', 'path5', 'path6', + 'path2', 'path4'] + dep_info1.Update(dep_info2, False) + self.assertEquals(expected_local_paths, dep_info1.local_paths) + diff --git a/tools/telemetry/catapult_base/dependency_manager.py b/tools/telemetry/catapult_base/dependency_manager_module.py similarity index 100% rename from tools/telemetry/catapult_base/dependency_manager.py rename to tools/telemetry/catapult_base/dependency_manager_module.py diff --git a/tools/telemetry/telemetry/core/exceptions.py b/tools/telemetry/telemetry/core/exceptions.py index dd218e14f0b3..9882f458aa9e 100644 --- a/tools/telemetry/telemetry/core/exceptions.py +++ b/tools/telemetry/telemetry/core/exceptions.py @@ -125,5 +125,7 @@ class UnknownPackageError(Error): class PackageDetectionError(Error): """ Represents an error when parsing an Android APK's package. """ + class AndroidDeviceParsingError(Error): """Represents an error when parsing output from an android device""" + -- 2.11.4.GIT