From b6e2fb17d0761e5b5a011b05204d6aa91be1b666 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 4 Aug 2022 11:15:26 -0500 Subject: [PATCH] Addon Manager: Refactor to improve testability --- src/Mod/AddonManager/Addon.py | 19 +++++++----- .../gui/test_workers_startup.py | 30 ++++++++++++++++++- .../addonmanager_workers_startup.py | 17 ++++++++--- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index 4749487801..e3ae0ad44d 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -120,6 +120,12 @@ class Addon: class ResolutionFailed(RuntimeError): """An exception type for dependency resolution failure.""" + # The location of Addon Manager cache files: overridden by testing code + cache_directory = os.path.join(FreeCAD.getUserCachePath(), "AddonManager") + + # The location of the Mod directory: overridden by testing code + mod_directory = os.path.join(FreeCAD.getUserAppDataDir(), "Mod") + def __init__(self, name: str, url: str, status: Status, branch: str): self.name = name.strip() self.display_name = self.name @@ -199,7 +205,7 @@ class Addon: def from_cache(cls, cache_dict: Dict): """Load basic data from cached dict data. Does not include Macro or Metadata information, which must be populated separately.""" - mod_dir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", cache_dict["name"]) + mod_dir = os.path.join(cls.mod_directory, cache_dict["name"]) if os.path.isdir(mod_dir): status = Addon.Status.UNCHECKED else: @@ -215,8 +221,7 @@ class Addon: if instance.repo_type == Addon.Kind.PACKAGE: # There must be a cached metadata file, too cached_package_xml_file = os.path.join( - FreeCAD.getUserCachePath(), - "AddonManager", + instance.cache_directory, "PackageMetadata", instance.name, ) @@ -458,7 +463,7 @@ class Addon: _, file_extension = os.path.splitext(real_icon) store = os.path.join( - FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata" + self.cache_directory, "PackageMetadata" ) self.cached_icon_filename = os.path.join( store, self.name, "cached_icon" + file_extension @@ -513,7 +518,7 @@ class Addon: """Check to see if the disabling stopfile exists""" stopfile = os.path.join( - FreeCAD.getUserAppDataDir(), "Mod", self.name, "ADDON_DISABLED" + self.mod_directory, self.name, "ADDON_DISABLED" ) return os.path.exists(stopfile) @@ -521,7 +526,7 @@ class Addon: """Disable this addon from loading when FreeCAD starts up by creating a stopfile""" stopfile = os.path.join( - FreeCAD.getUserAppDataDir(), "Mod", self.name, "ADDON_DISABLED" + mod_directory, self.name, "ADDON_DISABLED" ) with open(stopfile, "w", encoding="utf-8") as f: f.write( @@ -532,7 +537,7 @@ class Addon: """Re-enable loading this addon by deleting the stopfile""" stopfile = os.path.join( - FreeCAD.getUserAppDataDir(), "Mod", self.name, "ADDON_DISABLED" + self.mod_directory, self.name, "ADDON_DISABLED" ) try: os.unlink(stopfile) diff --git a/src/Mod/AddonManager/AddonManagerTest/gui/test_workers_startup.py b/src/Mod/AddonManager/AddonManagerTest/gui/test_workers_startup.py index c4104610c5..e6b636abe3 100644 --- a/src/Mod/AddonManager/AddonManagerTest/gui/test_workers_startup.py +++ b/src/Mod/AddonManager/AddonManagerTest/gui/test_workers_startup.py @@ -52,9 +52,18 @@ class TestWorkersStartup(unittest.TestCase): self.macro_counter = 0 self.workbench_counter = 0 self.prefpack_counter = 0 + + # Populated when the addon list is created in the first test + self.package_cache = {} + self.macro_cache = {} + + self.package_cache_file = tempfile.NamedTemporaryFile(mode='w', encoding="utf-8", delete=False) + self.macro_cache_file = tempfile.NamedTemporaryFile(mode='w', encoding="utf-8", delete=False) def test_create_addon_list_worker(self): - """ Test whether any addons are added: runs the full query, so this potentially is a SLOW test """ + """ Test whether any addons are added: runs the full query, so this potentially is a SLOW + test. Note that this test must be run before any of the other tests, so that the cache gets + created. """ worker = CreateAddonListWorker() worker.addon_repo.connect(self._addon_added) worker.start() @@ -65,6 +74,14 @@ class TestWorkersStartup(unittest.TestCase): self.assertGreater(self.workbench_counter,0, "No workbenches returned") self.assertGreater(self.prefpack_counter,0, "No preference packs returned") + # Write the cache data + if hasattr(self, "package_cache"): + self.package_cache_file.write(json.dumps(self.package_cache, indent=" ")) + self.package_cache_file.close() + if hasattr(self, "macro_cache"): + self.macro_cache_file.write(json.dumps(self.macro_cache, indent=" ")) + self.macro_cache_file.close() + def _addon_added(self, addon:Addon): """ Callback for adding an Addon: tracks the list, and counts the various types """ print (f"Addon Test: {addon.name}") @@ -76,3 +93,14 @@ class TestWorkersStartup(unittest.TestCase): if addon.contains_preference_pack(): self.prefpack_counter += 1 + # Also record the information for cache purposes + self.package_cache[addon.name] = addon.to_cache() + + if addon.macro is not None: + self.macro_cache.append(addon.macro.to_cache()) + + def test_load_packages_from_cache_worker(self): + pass + + def test_load_macros_from_cache_worker(self): + pass diff --git a/src/Mod/AddonManager/addonmanager_workers_startup.py b/src/Mod/AddonManager/addonmanager_workers_startup.py index ac6efcdb86..b98763db8a 100644 --- a/src/Mod/AddonManager/addonmanager_workers_startup.py +++ b/src/Mod/AddonManager/addonmanager_workers_startup.py @@ -393,16 +393,21 @@ class CreateAddonListWorker(QtCore.QThread): class LoadPackagesFromCacheWorker(QtCore.QThread): + """ A subthread worker that loads package information from its cache file. """ addon_repo = QtCore.Signal(object) def __init__(self, cache_file: str): QtCore.QThread.__init__(self) self.cache_file = cache_file - - def run(self): - metadata_cache_path = os.path.join( + self.metadata_cache_path = os.path.join( FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata" ) + + def override_metadata_cache_path(self, path): + """ For testing purposes, override the location to fetch the package metadata from. """ + self.metadata_cache_path = path + + def run(self): with open(self.cache_file, "r", encoding="utf-8") as f: data = f.read() if data: @@ -412,7 +417,7 @@ class LoadPackagesFromCacheWorker(QtCore.QThread): return repo = Addon.from_cache(item) repo_metadata_cache_path = os.path.join( - metadata_cache_path, repo.name, "package.xml" + self.metadata_cache_path, repo.name, "package.xml" ) if os.path.isfile(repo_metadata_cache_path): try: @@ -511,6 +516,10 @@ class UpdateChecker: self.basedir = FreeCAD.getUserAppDataDir() self.moddir = os.path.join(self.basedir, "Mod") + def override_mod_directory(self, moddir): + """ Primarily for use when testing, sets an alternate directory to use for mods """ + self.moddir = moddir + def check_workbench(self, wb): if not have_git or NOGIT: wb.set_status(Addon.Status.CANNOT_CHECK)