From 046c7a55b4cf7a53f8a80d145f954041ac8a22e8 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Tue, 16 Jan 2024 19:18:40 -0800 Subject: [PATCH] Add tests for habcache --- tests/conftest.py | 97 +- tests/distros/the_dcc/pre/.hab.json | 14 + tests/distros/the_dcc/pre/README.md | 3 + tests/hab_test_entry_points.py | 11 + tests/site/eps/site_habcache_cls.json | 9 + tests/site/site_cache_file.json | 5 + tests/site_main_check.habcache | 1433 +++++++++++++++++++++++++ tests/test_cache.py | 166 +++ tests/test_formatter.py | 4 +- tests/test_freeze.py | 2 +- tests/test_resolver.py | 12 + tests/test_scripts.py | 14 +- tests/test_site.py | 109 +- tests/test_solver.py | 8 +- tests/test_user_prefs.py | 50 +- tox.ini | 4 +- 16 files changed, 1893 insertions(+), 48 deletions(-) create mode 100644 tests/distros/the_dcc/pre/.hab.json create mode 100644 tests/distros/the_dcc/pre/README.md create mode 100644 tests/site/eps/site_habcache_cls.json create mode 100644 tests/site/site_cache_file.json create mode 100644 tests/site_main_check.habcache create mode 100644 tests/test_cache.py diff --git a/tests/conftest.py b/tests/conftest.py index 1076aa5..a6b66b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import json import os from contextlib import contextmanager from pathlib import Path, PurePath @@ -7,19 +8,109 @@ from hab import Resolver, Site +# Testing both cached and uncached every time adds extra testing time. This env +# var can be used to disable cached testing for local testing. +if os.getenv("HAB_TEST_UNCACHED_ONLY", "0") == "1": + resolver_tests = ["uncached"] +else: + resolver_tests = ["uncached", "cached"] -@pytest.fixture + +@pytest.fixture(scope="session") def config_root(): return Path(__file__).parent +def generate_habcached_site_file(config_root, dest): + """Returns the path to a site config file generated from `site_main.json` + configured so it can have a .habcache file generated next to it. The + config_paths and distro_paths of the site file are hard coded to point to + the repo's tests directory so it uses the same configs/distros. It also adds + a `config-root` entry to `platform_path_maps`. + """ + site_file = Path(dest) / "site.json" + site_src = config_root / "site_main.json" + + # Load the site_main.json files contents so we can modify it before saving + # it into the dest for testing. + data = json.load(site_src.open()) + append = data["append"] + + # Hard code relative_root to the tests folder so it works from + # a random testing directory without copying all configs/distros. + for key in ("config_paths", "distro_paths"): + for i in range(len(append[key])): + append[key][i] = append[key][i].format(relative_root=site_src.parent) + + # Add platform_path_maps for the pytest directory to simplify testing and + # test cross-platform support. We need to add all platforms, but this only + # needs to run on the current platform, so add the same path to all. + append["platform_path_maps"]["config-root"] = { + platform: str(config_root) for platform in data["set"]["platforms"] + } + + with site_file.open("w") as fle: + json.dump(data, fle, indent=4) + + return site_file + + +@pytest.fixture(scope="session") +def habcached_site_file(config_root, tmp_path_factory): + """Generates a site.json file and generates its habcache file. + This file is stored in a `_cache` directory in the pytest directory. + This persists for the entire testing session and can be used by other tests + that need to test hab when it is using a habcache. + """ + # Create the site file + shared = tmp_path_factory.mktemp("_cache") + ret = generate_habcached_site_file(config_root, shared) + + # Generate the habcache file + site = Site([ret]) + resolver = Resolver(site) + site.cache.save_cache(resolver, ret) + + return ret + + +@pytest.fixture +def habcached_resolver(habcached_site_file): + """Returns a Resolver using a habcache file that was generated for this session. + + See the `habcached_site_file` fixture for details on how the cache is setup. + For ease of testing the path to the saved habcache file is stored in + `_test_cache_file` on the returned resolver. + """ + site = Site([habcached_site_file]) + resolver = Resolver(site) + # Generate the cache and provide easy access to the habcache file path + resolver._test_cache_file = site.cache.site_cache_path(habcached_site_file) + + return resolver + + @pytest.fixture -def resolver(config_root): - """Return a standard testing resolver""" +def uncached_resolver(config_root): + """Return a standard testing resolver not using any habcache files.""" site = Site([config_root / "site_main.json"]) return Resolver(site=site) +@pytest.fixture(params=resolver_tests) +def resolver(request): + """Returns a hab.Resolver instance using the site_main.json site config file. + + This is a parameterized fixture that returns both cached and uncached versions + of the `site_main.json` site configuration. Note the cached version uses a + copy of it stored in the `_cache0` directory of the pytest temp files. This + should be used for most tests to ensure that all features are tested, but if + the test is not affected by caching you can use `uncached_resolver` instead. + """ + test_map = {"uncached": "uncached_resolver", "cached": "habcached_resolver"} + return request.getfixturevalue(test_map[request.param]) + + class Helpers(object): """A collection of reusable functions that tests can use.""" diff --git a/tests/distros/the_dcc/pre/.hab.json b/tests/distros/the_dcc/pre/.hab.json new file mode 100644 index 0000000..2c1bade --- /dev/null +++ b/tests/distros/the_dcc/pre/.hab.json @@ -0,0 +1,14 @@ +{ + "name": "the_dcc", + "environment": {}, + "distros": [ + "the_dcc_plugin_a>=1.0", + "the_dcc_plugin_b>=0.9", + "the_dcc_plugin_e" + ], + "aliases": { + "windows": [ + ["dcc", "{relative_root}\\the_dcc.exe"] + ] + } +} diff --git a/tests/distros/the_dcc/pre/README.md b/tests/distros/the_dcc/pre/README.md new file mode 100644 index 0000000..7f1c547 --- /dev/null +++ b/tests/distros/the_dcc/pre/README.md @@ -0,0 +1,3 @@ +This directory enables testing that hab ignores distro version folders defined +in the `ignored_distros` key of the site configuration. It's version is pulled +from the parent directory of the .hab.json file, which is named `pre`. diff --git a/tests/hab_test_entry_points.py b/tests/hab_test_entry_points.py index 57b7cdc..4b8f384 100644 --- a/tests/hab_test_entry_points.py +++ b/tests/hab_test_entry_points.py @@ -86,3 +86,14 @@ def uri_validate_project_b(resolver, uri): splits[0] = "PROJECT_B" uri = HabBase.separator.join(splits) return uri + + +class CacheVX: + """Used to validate that the entry_point `hab_habcache_cls` is respected by + raising an exception when initialized. + + Note: A real example of this class would subclass `hab.cache.Cache`. + """ + + def __init__(self, site): + raise NotImplementedError("hab_test_entry_points.CacheVX class was used") diff --git a/tests/site/eps/site_habcache_cls.json b/tests/site/eps/site_habcache_cls.json new file mode 100644 index 0000000..121ac79 --- /dev/null +++ b/tests/site/eps/site_habcache_cls.json @@ -0,0 +1,9 @@ +{ + "append": { + "entry_points": { + "hab.habcache_cls": { + "cls": "hab_test_entry_points:CacheVX" + } + } + } +} diff --git a/tests/site/site_cache_file.json b/tests/site/site_cache_file.json new file mode 100644 index 0000000..59b29c4 --- /dev/null +++ b/tests/site/site_cache_file.json @@ -0,0 +1,5 @@ +{ + "set": { + "site_cache_file_template": ".{{stem}}.hab_cache" + } +} diff --git a/tests/site_main_check.habcache b/tests/site_main_check.habcache new file mode 100644 index 0000000..dd1bfd0 --- /dev/null +++ b/tests/site_main_check.habcache @@ -0,0 +1,1433 @@ +{ + "version": 1, + "config_paths": { + "{config-root}/configs/*": { + "{config-root}/configs/app/app_aliased.json": { + "name": "aliased", + "context": [ + "app" + ], + "inherits": false, + "distros": [ + "aliased" + ] + }, + "{config-root}/configs/app/app_aliased_config.json": { + "name": "config", + "context": [ + "app", + "aliased" + ], + "inherits": true, + "alias_mods": { + "as_dict": { + "environment": { + "os_specific": true, + "linux": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/config", + "ALIASED_MOD_LOCAL_A": "Local Config A" + } + }, + "windows": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/config", + "ALIASED_MOD_LOCAL_A": "Local Config A" + } + } + } + } + } + }, + "{config-root}/configs/app/app_aliased_mod.json": { + "name": "mod", + "context": [ + "app", + "aliased" + ], + "inherits": true, + "distros": [ + "aliased", + "aliased_mod" + ], + "environment": { + "set": { + "CONFIG_DEFINED": "config_variable" + } + } + }, + "{config-root}/configs/app/app_aliased_mod_config.json": { + "name": "config", + "context": [ + "app", + "aliased", + "mod" + ], + "inherits": true, + "alias_mods": { + "as_dict": { + "environment": { + "os_specific": true, + "linux": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/config_mod", + "ALIASED_MOD_LOCAL_A": "Local Config Mod A" + } + }, + "windows": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/config_mod", + "ALIASED_MOD_LOCAL_A": "Local Config Mod A" + } + } + } + } + } + }, + "{config-root}/configs/app/app_houdini_a.json": { + "name": "a", + "context": [ + "app", + "houdini" + ], + "description": "Test that the duplicated alias houdini is resolved against 18.5.", + "inherits": false, + "distros": [ + "houdini18.5", + "houdini19.5" + ] + }, + "{config-root}/configs/app/app_houdini_b.json": { + "name": "b", + "context": [ + "app", + "houdini" + ], + "description": "Test that the duplicated alias houdini is resolved against 19.5.", + "inherits": false, + "distros": [ + "houdini19.5", + "houdini18.5" + ] + }, + "{config-root}/configs/app/app_maya.json": { + "name": "maya", + "context": [ + "app" + ], + "inherits": false, + "min_verbosity": { + "global": 2, + "hab": 2, + "hab-gui": 1 + } + }, + "{config-root}/configs/app/app_maya_2020.json": { + "name": "2020", + "context": [ + "app", + "maya" + ], + "inherits": true, + "distros": [ + "maya2020" + ] + }, + "{config-root}/configs/default/default.json": { + "name": "default", + "context": [], + "inherits": false, + "distros": { + "maya2020": [ + "the_dcc_plugin_a", + "the_dcc_plugin_b", + "the_dcc_plugin_c" + ], + "houdini18.5": [ + "the_dcc_plugin_d" + ] + } + }, + "{config-root}/configs/default/default_Sc1.json": { + "name": "Sc1", + "context": [ + "default" + ], + "inherits": false, + "distros": { + "maya2020": [ + "the_dcc_plugin_a", + "the_dcc_plugin_b", + "the_dcc_plugin_c" + ], + "houdini18.5": [ + "the_dcc_plugin_d" + ] + } + }, + "{config-root}/configs/default/default_Sc11.json": { + "name": "Sc11", + "context": [ + "default" + ], + "inherits": false, + "distros": { + "maya2020": [ + "the_dcc_plugin_a", + "the_dcc_plugin_b", + "the_dcc_plugin_c" + ], + "houdini18.5": [ + "the_dcc_plugin_d" + ] + } + }, + "{config-root}/configs/not_set/child.json": { + "name": "child", + "context": [ + "not_set" + ], + "environment": { + "unset": [ + "UNSET_VARIABLE" + ], + "set": { + "TEST": "case", + "FMT_FOR_OS": "a{;}b;c:{PATH!e}{;}d" + } + }, + "inherits": true + }, + "{config-root}/configs/not_set/env.json": { + "name": "env1", + "context": [ + "not_set" + ], + "environment": { + "unset": [ + "UNSET_VARIABLE", + "UNSET_VARIABLE_1", + "RELATIVE_VARIABLE" + ], + "set": { + "SET_RELATIVE": "{relative_root}", + "RELATIVE_VARIABLE": "{relative_root}/set", + "SET_VARIABLE": "set_value", + "MAYA_MODULE_PATH": "MMP_Set" + }, + "append": { + "RELATIVE_VARIABLE": "{relative_root}/append", + "APPEND_VARIABLE": "append_value" + }, + "prepend": { + "RELATIVE_VARIABLE": "{relative_root}/prepend", + "PREPEND_VARIABLE": "prepend_value" + } + }, + "inherits": true + }, + "{config-root}/configs/not_set/env_path_hab_uri.json": { + "name": "env_path_hab_uri", + "context": [ + "not_set" + ], + "environment": { + "set": { + "HAB_URI": "invalid value" + } + }, + "inherits": true + }, + "{config-root}/configs/not_set/env_path_set.json": { + "name": "env_path_set", + "context": [ + "not_set" + ], + "environment": { + "set": { + "PATH": "path_variable" + } + }, + "inherits": true + }, + "{config-root}/configs/not_set/env_path_unset.json": { + "name": "env_path_unset", + "context": [ + "not_set" + ], + "environment": { + "unset": [ + "PATH" + ] + }, + "inherits": true + }, + "{config-root}/configs/not_set/not_set.json": { + "name": "not_set", + "context": [], + "inherits": false, + "distros": [ + "aliased", + "maya2020" + ] + }, + "{config-root}/configs/not_set/not_set_distros.json": { + "name": "distros", + "context": [ + "not_set" + ], + "inherits": false, + "distros": { + "the_dcc": [] + }, + "environment": { + "append": { + "Test": "Case" + } + } + }, + "{config-root}/configs/not_set/not_set_empty_lists.json": { + "name": "empty_lists", + "context": [ + "not_set" + ], + "inherits": false, + "aliases": [], + "distros": [] + }, + "{config-root}/configs/not_set/not_set_no_distros.json": { + "name": "no_distros", + "context": [ + "not_set" + ], + "inherits": false, + "environment": { + "append": { + "Test": "Case" + } + } + }, + "{config-root}/configs/not_set/not_set_no_env.json": { + "name": "no_env", + "context": [ + "not_set" + ], + "inherits": false, + "distros": { + "the_dcc": [] + } + }, + "{config-root}/configs/not_set/os_env.json": { + "name": "os", + "context": [ + "not_set" + ], + "environment": { + "os_specific": true, + "linux": { + "unset": [ + "UNSET_VARIABLE_LIN" + ], + "set": { + "SET_VARIABLE_LIN": "set_value_lin" + }, + "append": { + "APPEND_VARIABLE_LIN": "append_value_lin" + }, + "prepend": { + "PREPEND_VARIABLE_LIN": "prepend_value_lin" + } + }, + "windows": { + "unset": [ + "UNSET_VARIABLE_WIN" + ], + "set": { + "SET_VARIABLE_WIN": "set_value_win" + }, + "append": { + "APPEND_VARIABLE_WIN": "append_value_win" + }, + "prepend": { + "PREPEND_VARIABLE_WIN": "prepend_value_win" + } + } + }, + "inherits": false + }, + "{config-root}/configs/place-holder/place-holder_child.json": { + "name": "child", + "context": [ + "place-holder" + ], + "inherits": false, + "distros": [ + "the_dcc" + ] + }, + "{config-root}/configs/place-holder/place-holder_inherits.json": { + "name": "inherits", + "context": [ + "place-holder" + ], + "inherits": true, + "environment": { + "set": { + "TEST": "case" + } + } + }, + "{config-root}/configs/project_a/project_a.json": { + "name": "project_a", + "context": [], + "inherits": false, + "distros": [ + "maya2020", + "houdini18.5" + ] + }, + "{config-root}/configs/project_a/project_a_Sc001.json": { + "name": "Sc001", + "context": [ + "project_a" + ], + "inherits": true, + "distros": [ + "maya2020==2020.0" + ] + }, + "{config-root}/configs/project_a/project_a_Sc001_animation.json": { + "name": "Animation", + "context": [ + "project_a", + "Sc001" + ], + "inherits": true, + "distros": [ + "maya2020" + ] + }, + "{config-root}/configs/project_a/project_a_Sc001_rigging.json": { + "name": "Rigging", + "context": [ + "project_a", + "Sc001" + ], + "inherits": true, + "distros": [ + "maya2020" + ] + }, + "{config-root}/configs/verbosity/verbosity.json": { + "name": "verbosity", + "context": [], + "description": "Defines verbosity settings that can be inherited", + "environment": { + "set": { + "VERBOSITY": "verbosity" + } + }, + "inherits": false, + "min_verbosity": { + "global": 2, + "hab-gui": 1 + } + }, + "{config-root}/configs/verbosity/verbosity_hidden.json": { + "name": "hidden", + "context": [ + "verbosity" + ], + "description": "Verbosity global setting above the normal max of 2. Should only be visible if verbosity is None.", + "inherits": true, + "environment": { + "set": { + "VERBOSITY": "verbosity/hidden" + } + }, + "min_verbosity": { + "global": 3, + "hab-gui": 2 + } + }, + "{config-root}/configs/verbosity/verbosity_inherit-no.json": { + "name": "inherit-no", + "context": [ + "verbosity" + ], + "description": "Doesn't define verbosity and doesn't inherit it from parent.", + "inherits": false, + "environment": { + "set": { + "VERBOSITY": "verbosity/inherit-no" + } + } + }, + "{config-root}/configs/verbosity/verbosity_inherit-override.json": { + "name": "inherit-override", + "context": [ + "verbosity" + ], + "description": "Inherits from parent but defines verbosity on itself.", + "inherits": true, + "environment": { + "set": { + "VERBOSITY": "verbosity/inherit-override" + } + }, + "min_verbosity": { + "global": 1 + } + }, + "{config-root}/configs/verbosity/verbosity_inherit.json": { + "name": "inherit", + "context": [ + "verbosity" + ], + "description": "Doesn't define verbosity but does inherit it from parent.", + "distros": [ + "aliased_verbosity" + ], + "inherits": true, + "environment": { + "set": { + "VERBOSITY": "verbosity/inherit" + } + } + } + } + }, + "distro_paths": { + "{config-root}/distros/*": { + "{config-root}/distros/aliased/2.0/.hab.json": { + "name": "aliased", + "aliases": { + "linux": [ + [ + "as_dict", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/test" + } + } + } + ], + [ + "inherited", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "append": { + "PATH": [ + "{PATH!e}", + "{relative_root}/PATH/env/with spaces", + "/mnt/shared_resources/with spaces" + ] + } + } + } + ], + [ + "as_list", + [ + "python", + "{relative_root}/list_vars.py" + ] + ], + [ + "as_str", + "python" + ], + [ + "global", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "append": { + "ALIASED_GLOBAL_A": "Local A Append" + }, + "prepend": { + "ALIASED_GLOBAL_A": "Local A Prepend" + }, + "set": { + "ALIASED_GLOBAL_C": "Local C Set" + }, + "unset": [ + "ALIASED_GLOBAL_D" + ] + } + } + ] + ], + "windows": [ + [ + "as_dict", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/test" + } + } + } + ], + [ + "inherited", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "append": { + "PATH": [ + "{PATH!e}", + "{relative_root}/PATH/env/with spaces", + "\\\\example\\shared_resources\\with spaces" + ] + } + } + } + ], + [ + "as_list", + [ + "python", + "{relative_root}/list_vars.py" + ] + ], + [ + "as_str", + "python" + ], + [ + "global", + { + "cmd": [ + "python", + "{relative_root}/list_vars.py" + ], + "environment": { + "append": { + "ALIASED_GLOBAL_A": "Local A Append" + }, + "prepend": { + "ALIASED_GLOBAL_A": "Local A Prepend" + }, + "set": { + "ALIASED_GLOBAL_C": "Local C Set" + }, + "unset": [ + "ALIASED_GLOBAL_D" + ] + } + } + ] + ] + }, + "environment": { + "append": { + "ALIASED_GLOBAL_A": "Global A" + }, + "set": { + "ALIASED_GLOBAL_B": "Global B", + "ALIASED_GLOBAL_C": "Global C", + "ALIASED_GLOBAL_D": "Global D", + "ALIASED_GLOBAL_F": "Global F" + }, + "unset": [ + "ALIASED_GLOBAL_E" + ] + }, + "version": "2.0" + }, + "{config-root}/distros/aliased_mod/1.0/.hab.json": { + "name": "aliased_mod", + "description": "Modifies the aliases defined in the aliased distro.", + "alias_mods": { + "as_dict": { + "environment": { + "os_specific": true, + "linux": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/modified", + "ALIASED_MOD_LOCAL_A": "Local Mod A" + } + }, + "windows": { + "prepend": { + "ALIASED_LOCAL": "{relative_root}/modified", + "ALIASED_MOD_LOCAL_A": "Local Mod A" + } + } + } + }, + "as_list": { + "environment": { + "os_specific": true, + "linux": { + "prepend": { + "ALIASED_MOD_LOCAL_B": "Local Mod B" + } + }, + "windows": { + "prepend": { + "ALIASED_MOD_LOCAL_B": "Local Mod B" + } + } + } + }, + "global": { + "environment": { + "os_specific": true, + "linux": { + "prepend": { + "ALIASED_GLOBAL_A": "Local Mod A", + "ALIASED_MOD_LOCAL_B": "Local Mod B", + "ALIASED_GLOBAL_F": "Local Mod F" + } + }, + "windows": { + "prepend": { + "ALIASED_GLOBAL_A": "Local Mod A", + "ALIASED_MOD_LOCAL_B": "Local Mod B", + "ALIASED_GLOBAL_F": "Local Mod F" + } + } + } + } + }, + "environment": { + "set": { + "ALIASED_MOD_GLOBAL_A": "Global Mod A" + } + }, + "version": "1.0" + }, + "{config-root}/distros/aliased_verbosity/1.0/.hab.json": { + "name": "aliased_verbosity", + "aliases": { + "linux": [ + [ + "vb_default", + [ + "python" + ] + ], + [ + "vb0", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 0, + "hab-gui": 3 + } + } + ], + [ + "vb1", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 1, + "hab-gui": 2 + } + } + ], + [ + "vb2", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 2, + "hab-gui": 1 + } + } + ], + [ + "vb3", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 3, + "hab-gui": 0 + } + } + ] + ], + "windows": [ + [ + "vb_default", + [ + "python" + ] + ], + [ + "vb0", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 0, + "hab-gui": 3 + } + } + ], + [ + "vb1", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 1, + "hab-gui": 2 + } + } + ], + [ + "vb2", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 2, + "hab-gui": 1 + } + } + ], + [ + "vb3", + { + "cmd": [ + "python" + ], + "min_verbosity": { + "global": 3, + "hab-gui": 0 + } + } + ] + ] + }, + "version": "1.0" + }, + "{config-root}/distros/all_settings/0.1.0.dev1/.hab.json": { + "name": "all_settings", + "version": "0.1.0.dev1", + "environment": { + "unset": [ + "OTHER" + ], + "set": { + "TEST": "case" + }, + "append": { + "MAYA_MODULE_PATH": "." + }, + "prepend": { + "TEST_VARIABLE": "prepend_value" + } + }, + "distros": [ + "Maya2020" + ], + "aliases": { + "windows": [ + [ + "maya", + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe" + ], + [ + "mayapy", + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe" + ] + ] + } + }, + "{config-root}/distros/houdini18.5/18.5.351/.hab.json": { + "name": "houdini18.5", + "version": "18.5.351", + "aliases": { + "linux": [ + [ + "houdini", + "/opt/hfs18.5.351/bin/houdini" + ], + [ + "houdini18.5", + { + "cmd": "/opt/hfs18.5.351/bin/houdini", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "houdinicore", + { + "cmd": "/opt/hfs18.5.351/bin/houdinicore", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "hhoudinicore18.5", + { + "cmd": "/opt/hfs18.5.351/bin/houdinicore", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk", + { + "cmd": "/opt/hfs18.5.351/bin/husk", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk18.5", + { + "cmd": "/opt/hfs18.5.351/bin/husk", + "min_verbosity": { + "global": 1 + } + } + ] + ], + "windows": [ + [ + "houdini", + "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\houdini.exe" + ], + [ + "houdini18.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\houdini.exe", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "houdinicore", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\houdinicore.exe" + } + ], + [ + "houdinicore18.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\houdinicore.exe", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\husk.exe", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk18.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 18.5.351\\bin\\husk.exe", + "min_verbosity": { + "global": 1 + } + } + ] + ] + } + }, + "{config-root}/distros/houdini19.5/19.5.493/.hab.json": { + "name": "houdini19.5", + "version": "19.5.493", + "aliases": { + "linux": [ + [ + "houdini", + "/opt/hfs19.5.493/bin/houdini" + ], + [ + "houdini19.5", + { + "cmd": "/opt/hfs19.5.493/bin/houdini", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "houdinicore", + { + "cmd": "/opt/hfs19.5.493/bin/houdinicore", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "hhoudinicore19.5", + { + "cmd": "/opt/hfs19.5.493/bin/houdinicore", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk", + { + "cmd": "/opt/hfs19.5.493/bin/husk", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk19.5", + { + "cmd": "/opt/hfs19.5.493/bin/husk", + "min_verbosity": { + "global": 1 + } + } + ] + ], + "windows": [ + [ + "houdini", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe", + "hab.launch_cls": { + "subprocess": "subprocess:Popen" + } + } + ], + [ + "houdini19.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe", + "min_verbosity": { + "global": 1 + }, + "hab.launch_cls": { + "subprocess": "subprocess:Popen" + } + } + ], + [ + "houdinicore", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe", + "hab.launch_cls": { + "subprocess": "subprocess:Popen" + } + } + ], + [ + "houdinicore19.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe", + "min_verbosity": { + "global": 1 + }, + "hab.launch_cls": { + "subprocess": "subprocess:Popen" + } + } + ], + [ + "husk", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\husk.exe", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "husk19.5", + { + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\husk.exe", + "min_verbosity": { + "global": 1 + } + } + ] + ] + } + }, + "{config-root}/distros/maya/2020.0/.hab.json": { + "name": "maya2020", + "environment": {}, + "aliases": { + "windows": [ + [ + "maya", + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe" + ], + [ + "mayapy", + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe" + ] + ] + }, + "version": "2020.0" + }, + "{config-root}/distros/maya/2020.1/.hab.json": { + "name": "maya2020", + "version": "2020.1", + "environment": {}, + "aliases": { + "windows": [ + [ + "maya", + { + "cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe", + "icon": "C:\\Windows\\Installer\\{{0EBFFCF6-F972-4D40-863F-E673B5C38236}}\\maya.ico" + } + ], + [ + "mayapy", + { + "cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe", + "min_verbosity": { + "global": 2 + } + } + ], + [ + "maya20", + { + "cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe", + "icon": "C:\\Windows\\Installer\\{{0EBFFCF6-F972-4D40-863F-E673B5C38236}}\\maya.ico", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "mayapy20", + { + "cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe", + "min_verbosity": { + "global": 2 + } + } + ], + [ + "pip", + { + "cmd": [ + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe", + "-m", + "pip" + ], + "min_verbosity": { + "global": 2 + } + } + ] + ], + "linux": [ + [ + "maya", + { + "cmd": "/usr/local/bin/maya2020" + } + ], + [ + "mayapy", + { + "cmd": "/usr/local/bin/mayapy2020", + "min_verbosity": { + "global": 2 + } + } + ], + [ + "maya20", + { + "cmd": "/usr/local/bin/maya2020", + "min_verbosity": { + "global": 1 + } + } + ], + [ + "mayapy20", + { + "cmd": "/usr/local/bin/mayapy2020", + "min_verbosity": { + "global": 2 + } + } + ], + [ + "pip", + { + "cmd": [ + "/usr/local/bin/mayapy2020", + "-m", + "pip" + ], + "min_verbosity": { + "global": 2 + } + } + ] + ] + } + }, + "{config-root}/distros/the_dcc/1.0/.hab.json": { + "name": "the_dcc", + "environment": {}, + "distros": [ + "the_dcc_plugin_a>=1.0", + "the_dcc_plugin_b>=0.9", + "the_dcc_plugin_e" + ], + "aliases": { + "windows": [ + [ + "dcc", + "{relative_root}\\the_dcc.exe" + ] + ] + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc/1.1/.hab.json": { + "name": "the_dcc", + "environment": {}, + "distros": [ + "the_dcc_plugin_a>=1.0" + ], + "aliases": { + "windows": [ + [ + "dcc", + "{relative_root}\\the_dcc.exe" + ] + ] + }, + "version": "1.1" + }, + "{config-root}/distros/the_dcc/1.2/.hab.json": { + "name": "the_dcc", + "environment": {}, + "distros": [ + "the_dcc_plugin_a>=1.0", + "the_dcc_plugin_b>=0.9", + "the_dcc_plugin_e" + ], + "aliases": { + "linux": [ + [ + "dcc", + "{relative_root}//the_dcc" + ] + ], + "windows": [ + [ + "dcc", + "{relative_root}\\the_dcc.exe" + ] + ] + }, + "version": "1.2" + }, + "{config-root}/distros/the_dcc_plugin_a/0.9/.hab.json": { + "name": "the_dcc_plugin_a", + "distros": [ + "the_dcc_plugin_d", + "the_dcc_plugin_e<2.0", + "the_dcc<1.2" + ], + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "0.9" + }, + "{config-root}/distros/the_dcc_plugin_a/1.0/.hab.json": { + "name": "the_dcc_plugin_a", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc_plugin_a/1.1/.hab.json": { + "name": "the_dcc_plugin_a", + "distros": [ + "the_dcc_plugin_e<2.0", + "the_dcc_plugin_d" + ], + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}", + "DCC_CONFIG_PATH": "{relative_root}" + } + }, + "version": "1.1" + }, + "{config-root}/distros/the_dcc_plugin_b/0.9/.hab.json": { + "name": "the_dcc_plugin_b", + "distros": [ + "the_dcc<1.2" + ], + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "0.9" + }, + "{config-root}/distros/the_dcc_plugin_b/1.0/.hab.json": { + "name": "the_dcc_plugin_b", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc_plugin_b/1.1/.hab.json": { + "name": "the_dcc_plugin_b", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + }, + "prepend": { + "DCC_CONFIG_PATH": "{relative_root}" + } + }, + "version": "1.1" + }, + "{config-root}/distros/the_dcc_plugin_c/0.9/.hab.json": { + "name": "the_dcc_plugin_c", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "0.9" + }, + "{config-root}/distros/the_dcc_plugin_c/1.0/.hab.json": { + "name": "the_dcc_plugin_c", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc_plugin_c/1.1/.hab.json": { + "name": "the_dcc_plugin_c", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.1" + }, + "{config-root}/distros/the_dcc_plugin_d/0.9/.hab.json": { + "name": "the_dcc_plugin_d", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "0.9" + }, + "{config-root}/distros/the_dcc_plugin_d/1.0/.hab.json": { + "name": "the_dcc_plugin_d", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc_plugin_d/1.1/.hab.json": { + "name": "the_dcc_plugin_d", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + }, + "prepend": { + "DCC_CONFIG_PATH": "{relative_root}" + } + }, + "version": "1.1" + }, + "{config-root}/distros/the_dcc_plugin_e/0.9/.hab.json": { + "name": "the_dcc_plugin_e", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "0.9" + }, + "{config-root}/distros/the_dcc_plugin_e/1.0/.hab.json": { + "name": "the_dcc_plugin_e", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}" + } + }, + "version": "1.0" + }, + "{config-root}/distros/the_dcc_plugin_e/1.1/.hab.json": { + "name": "the_dcc_plugin_e", + "environment": { + "append": { + "DCC_MODULE_PATH": "{relative_root}", + "DCC_CONFIG_PATH": "{relative_root}" + } + }, + "version": "1.1" + } + } + } +} diff --git a/tests/test_cache.py b/tests/test_cache.py new file mode 100644 index 0000000..d38708a --- /dev/null +++ b/tests/test_cache.py @@ -0,0 +1,166 @@ +import json +from pathlib import Path + +from hab import Resolver, Site +from hab.cache import Cache +from hab.parsers import Config, DistroVersion, HabBase + + +def test_cached_keys(uncached_resolver): + check = { + "config_paths": ("*.json", Config), + "distro_paths": ("*/.hab.json", DistroVersion), + } + + cache = uncached_resolver.site.cache + # Container variable is not set by default + assert not hasattr(cache, "_cached_keys") + # On first call generates the correct return value + assert cache.cached_keys == check + # And stored the value in the container variable + assert hasattr(cache, "_cached_keys") + + # Verify that the cached value is returned once generated + cache._cached_keys = "Test value" + assert cache.cached_keys == "Test value" + + +def test_site_cache_path(config_root, uncached_resolver, tmpdir): + cache = uncached_resolver.site.cache + + # Test default + site_file = Path(tmpdir) / "test.json" + assert cache.cache_template == "{stem}.habcache" + assert cache.site_cache_path(site_file) == tmpdir / "test.habcache" + + # Test that cache_template is respected + cache.cache_template = "<{stem}>.ext" + assert cache.site_cache_path(site_file) == tmpdir / ".ext" + + # Test that `site_cache_file_template` site config setting is respected + site = Site([config_root / "site" / "site_cache_file.json"]) + assert site.cache.cache_template == ".{stem}.hab_cache" + + +def test_save_cache(config_root, tmpdir, habcached_resolver): + # Check that the habcache file generated the expected output text + # Note: This file will need updated as the test configuration is updated + check = (config_root / "site_main_check.habcache").open().readlines() + cache = habcached_resolver._test_cache_file.open().readlines() + # Add trailing white space to match template file's trailing white space + cache[-1] += "\n" + assert len(cache) == len(check) + for i in range(len(cache)): + assert cache[i] == check[i] + + +def test_load_cache(config_root, uncached_resolver, habcached_site_file): + """Tests non-cached resolved data matches a reference cached version.""" + cached_site = Site([habcached_site_file]) + cached_resolver = Resolver(cached_site) + + # Load the reference cache. In normal operation _cache is None until the + # first time `.cache` is called, but for this test we won't be doing that. + cached_site.cache._cache = {} + cached_site.cache.load_cache(config_root / "site_main_check.habcache") + + # Check that un-cached resolver settings match the reference cache + for key in ("config_paths", "distro_paths"): + assert getattr(uncached_resolver, key) == getattr(cached_resolver, key) + + +def test_unsupported_version_warning(uncached_resolver, tmpdir, caplog): + cache_file = Path(tmpdir) / "test.habcache" + warn_msg = ( + "File is using a unsupported habcache version {}. " + f"Only versions > {{}} are supported, ignoring {cache_file}" + ) + + def _load_cache_version(version, cls=Cache): + # Generate a test cache for the given version number + with cache_file.open("w") as fle: + json.dump({"version": version}, fle) + + cache = cls(uncached_resolver.site) + cache._cache = {} + caplog.clear() + cache.load_cache(cache_file) + return [rec.message for rec in caplog.records] + + # No warning if version is supported + assert _load_cache_version(1) == [] + # Warning logged if cache version is newer than supported_version + assert _load_cache_version(2) == [warn_msg.format(2, 1)] + + # Warning is logged correctly for higher supported versions + class CacheV2(Cache): + supported_version = 2 + + assert _load_cache_version(1, cls=CacheV2) == [] + assert _load_cache_version(2, cls=CacheV2) == [] + assert _load_cache_version(3, cls=CacheV2) == [warn_msg.format(3, 2)] + + +def test_cached_method(config_root, habcached_site_file): + """Test the Cache.cache method options.""" + cached_site = Site([habcached_site_file]) + cache = cached_site.cache + assert cache.enabled is True + + # At this point _cache is None + assert cache._cache is None + # Simulate the cache already being loaded + cache._cache = "Test value" + assert cache.cache() == "Test value" + + # Forcing the cache to reload, re-generates a cache + result = cache.cache(force=True) + assert isinstance(result, dict) + assert len(result) + + # Check that disabling caching causes the cache to not be returned + cache.enabled = False + assert cache.enabled is False + result = cache.cache() + assert isinstance(result, dict) + assert not len(result) + + # Check that HabBase._cache returns a empty dict. + assert HabBase(None, None)._cache() == {} + + +def test_resolver_cache(request, resolver): + """Tests that cached and uncached resolvers actually use/don't use the cache. + + `uncached_resolver` should not have a habcache file and shouldn't use a cache. + `habcached_resolver` should have a habcache and uses it. + """ + # Figure out if this is the cached or uncached resolver test + is_cached = "habcached_resolver" in request.fixturenames + + # Get the site file path + assert len(resolver.site.paths) == 1 + site_file = resolver.site.paths[0] + cache_file = resolver.site.cache.site_cache_path(site_file) + + # The .habcache file should only exist when testing cached + if is_cached: + assert cache_file.exists() + else: + assert not cache_file.exists() + + # force the resolver to load config/distro information + resolver.resolve("not_set") + + cache = resolver.site.cache._cache + if is_cached: + # This habcache setup has exactly one cached glob string + assert len(cache["config_paths"]) == 1 + assert len(cache["distro_paths"]) == 1 + # The flat cache has many configs/distros, the test only needs to ensure + # that we have gotten some results + assert len(cache["flat"]["config_paths"]) > 10 + assert len(cache["flat"]["distro_paths"]) > 10 + else: + # If there aren't any habcache files, a default dict is returned + assert cache == {"flat": {"config_paths": {}, "distro_paths": {}}} diff --git a/tests/test_formatter.py b/tests/test_formatter.py index 8758b81..17cd8fc 100644 --- a/tests/test_formatter.py +++ b/tests/test_formatter.py @@ -53,9 +53,9 @@ def test_language_from_ext(monkeypatch): assert Formatter.language_from_ext("") == "sh" -def test_format_environment_value(resolver): +def test_format_environment_value(uncached_resolver): forest = {} - config = Config(forest, resolver) + config = Config(forest, uncached_resolver) # test_format_environment_value doesn't replace the special formatters. # This allows us to delay these formats to only when creating the final diff --git a/tests/test_freeze.py b/tests/test_freeze.py index 7c41adb..353aca3 100644 --- a/tests/test_freeze.py +++ b/tests/test_freeze.py @@ -140,7 +140,7 @@ def test_unfreeze(config_root, resolver): assert cfg.alias_mods is NotSet -def test_decode_freeze(config_root, resolver): +def test_decode_freeze(config_root): check_file = config_root / "frozen_no_distros.json" checks = utils.json.load(check_file.open()) v1 = checks["version1"] diff --git a/tests/test_resolver.py b/tests/test_resolver.py index faf8006..4d22c35 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -749,6 +749,18 @@ def test_clear_caches(resolver): assert resolver._distros is None +def test_clear_caches_cached(habcached_resolver): + """Test that Resolver.clear_cache works when using a habcache.""" + + # Populate resolver cache data + habcached_resolver.resolve("not_set") + assert isinstance(habcached_resolver.site.cache._cache, dict) + assert len(habcached_resolver.site.cache._cache) + + habcached_resolver.clear_caches() + assert habcached_resolver.site.cache._cache is None + + def test_uri_validate(config_root): """Test the `hab.uri.validate` entry_point.""" resolver = Resolver( diff --git a/tests/test_scripts.py b/tests/test_scripts.py index b4b3fcf..0e3e3c3 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -14,7 +14,7 @@ @pytest.mark.parametrize("reference_name", reference_names) -def test_scripts(resolver, tmpdir, monkeypatch, config_root, reference_name): +def test_scripts(uncached_resolver, tmpdir, monkeypatch, config_root, reference_name): """Checks all of the scripts HabBase.write_script generates for a specified set of arguments. @@ -62,7 +62,7 @@ def test_scripts(resolver, tmpdir, monkeypatch, config_root, reference_name): assert platform in ("linux", "osx", "win32") monkeypatch.setattr(utils, "Platform", utils.BasePlatform.get_platform(platform)) - cfg = resolver.resolve(spec["uri"]) + cfg = uncached_resolver.resolve(spec["uri"]) reference = config_root / "reference_scripts" / reference_name ext = spec["ext"] @@ -172,7 +172,7 @@ def walk_dir(current): @pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest") -def test_complex_alias_bat(tmpdir, config_root, resolver): +def test_complex_alias_bat(tmpdir, config_root): """This test is a placeholder for a future that can actually call hab's `hab.cli` and its aliases to check that they function correctly in Batch. @@ -209,7 +209,7 @@ def test_complex_alias_bat(tmpdir, config_root, resolver): @pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest") -def test_complex_alias_ps1(tmpdir, config_root, resolver): +def test_complex_alias_ps1(tmpdir, config_root): """This test is a placeholder for a future that can actually call hab's `hab.cli` and its aliases to check that they function correctly in PowerShell. @@ -246,7 +246,7 @@ def test_complex_alias_ps1(tmpdir, config_root, resolver): @pytest.mark.skip(reason="Find a way to test complex alias evaluation in pytest") -def test_complex_alias_sh(tmpdir, config_root, resolver): +def test_complex_alias_sh(tmpdir, config_root): """This test is a placeholder for a future that can actually call hab's `hab.cli` and its aliases to check that they function correctly in Bash. @@ -283,14 +283,14 @@ def test_complex_alias_sh(tmpdir, config_root, resolver): @pytest.mark.parametrize("ext", (".bat", ".ps1", ".sh")) -def test_invalid_alias(resolver, tmpdir, ext): +def test_invalid_alias(uncached_resolver, tmpdir, ext): """Check that useful errors are raised if an invalid alias is passed or if the alias doesn't have "cmd" defined. """ kwargs = dict(ext=ext, exit=True, args=None, create_launch=True) # Check that calling a bad alias name raises a useful error message - cfg = resolver.resolve("not_set/child") + cfg = uncached_resolver.resolve("not_set/child") with pytest.raises(errors.HabError, match=r'"bad-alias" is not a valid alias name'): cfg.write_script(str(tmpdir), launch="bad-alias", **kwargs) diff --git a/tests/test_site.py b/tests/test_site.py index daa889d..884e83b 100644 --- a/tests/test_site.py +++ b/tests/test_site.py @@ -1,10 +1,11 @@ import sys from pathlib import Path, PurePosixPath, PureWindowsPath -import colorama import pytest +from colorama import Fore, Style from hab import Resolver, Site, utils +from hab.cache import Cache def test_environment_variables(config_root, monkeypatch): @@ -258,10 +259,7 @@ def test_dump(config_root): result = site.dump() for check in checks: - assert ( - check.format(green=colorama.Fore.GREEN, reset=colorama.Style.RESET_ALL) - in result - ) + assert check.format(green=Fore.GREEN, reset=Style.RESET_ALL) in result paths = [config_root / "site_override.json"] site = Site(paths) @@ -272,6 +270,65 @@ def test_dump(config_root): assert check.format(green="", reset="") in result +def test_dump_cached(config_root, habcached_site_file): + """Test that cached indicators are shown properly for site dump based on + verbosity setting.""" + + # Create the hab setup with caching only on one of the two site files + other_site = config_root / "site_os_specific.json" + site = Site([habcached_site_file, other_site]) + resolver = Resolver(site) + site.cache.save_cache(resolver, habcached_site_file) + + # Build a check string to verify that the dump is correctly formatted + # Note: To simplify the check_template for testing we will force the dump + # to a smaller width to ensure it always wraps the file paths. + platform = utils.Platform.name() + check_template = ( + f"{{green}}HAB_PATHS: {{reset}}{habcached_site_file}{{cached}}", + f" {other_site}", + f"{{green}}config_paths: {{reset}}config\\path\\{platform}", + f" {config_root}\\configs\\*{{cached}}", + f"{{green}}distro_paths: {{reset}}distro\\path\\{platform}", + f" {config_root}\\distros\\*{{cached}}", + ) + check_template = "\n".join(check_template) + colors = { + "green": Fore.GREEN, + "reset": Style.RESET_ALL, + } + if platform != "windows": + check_template = check_template.replace("\\", "/") + + # With color enabled: + # No verbosity, should not show cached status + assert site.get("colorize") is None + result = site.dump(width=60) + check = check_template.format(cached="", **colors) + assert check in result + + # verbosity enabled, should show cached status + result = site.dump(verbosity=1, width=60) + check = check_template.format( + cached=f" {Fore.YELLOW}(cached){Style.RESET_ALL}", **colors + ) + assert check in result + + # Disable Color: + site["colorize"] = False + assert site.get("colorize") is False + + # No verbosity, should not show cached status + result = site.dump(width=60) + check = check_template.format(cached="", green="", reset="") + assert check in result + + # verbosity enabled, should show cached status + result = site.dump(verbosity=1, width=60) + check = check_template.format(cached=" (cached)", green="", reset="") + assert check in result + + class TestOsSpecific: def test_linux(self, monkeypatch, config_root): """Check that if "os_specific" is set to true, only vars for the current @@ -324,16 +381,28 @@ def test_linux(self, monkeypatch, config_root): assert out == "/usr/local/host/root" out = site.platform_path_map("/usr/local/host/root/extra", platform="linux") assert out == "/usr/local/host/root/extra" + out = site.platform_path_key("/usr/local/host/root", platform="linux") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key("/usr/local/host/root/extra", platform="linux") + assert out.as_posix() == "{host-root}/extra" out = site.platform_path_map("/usr/local/host/root", platform="osx") assert out == "/usr/local/osx/host/root" out = site.platform_path_map("/usr/local/host/root/extra", platform="osx") assert out == "/usr/local/osx/host/root/extra" + out = site.platform_path_key("/usr/local/host/root", platform="osx") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key("/usr/local/host/root/extra", platform="osx") + assert out.as_posix() == "{host-root}/extra" out = site.platform_path_map("/usr/local/host/root", platform="windows") assert out == r"c:\host\root" out = site.platform_path_map("/usr/local/host/root/extra", platform="windows") assert out == r"c:\host\root\extra" + out = site.platform_path_key("/usr/local/host/root", platform="windows") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key("/usr/local/host/root/extra", platform="windows") + assert out.as_posix() == "{host-root}/extra" def test_win(self, monkeypatch, config_root): """For windows check that various inputs are correctly processed.""" @@ -347,6 +416,10 @@ def test_win(self, monkeypatch, config_root): assert out == "/usr/local/host/root/extra" out = site.platform_path_map("c:/host/root", platform="linux") assert out == "/usr/local/host/root" + out = site.platform_path_key(r"c:\host\root", platform="linux") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key(r"c:\host\root\extra", platform="linux") + assert out.as_posix() == "{host-root}/extra" out = site.platform_path_map(r"c:\host\root", platform="osx") assert out == "/usr/local/osx/host/root" @@ -354,6 +427,10 @@ def test_win(self, monkeypatch, config_root): assert out == "/usr/local/osx/host/root/extra" out = site.platform_path_map("c:/host/root", platform="osx") assert out == "/usr/local/osx/host/root" + out = site.platform_path_key(r"c:\host\root", platform="osx") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key(r"c:\host\root\extra", platform="osx") + assert out.as_posix() == "{host-root}/extra" out = site.platform_path_map(r"c:\host\root", platform="windows") assert out == r"c:\host\root" @@ -361,6 +438,16 @@ def test_win(self, monkeypatch, config_root): assert out == r"c:\host\root\extra" out = site.platform_path_map("c:/host/root", platform="windows") assert out == r"c:\host\root" + out = site.platform_path_key(r"c:\host\root", platform="windows") + assert out.as_posix() == "{host-root}" + out = site.platform_path_key(r"c:\host\root\extra", platform="windows") + assert out.as_posix() == "{host-root}/extra" + + def test_unset_variables(self, config_root): + """Don't modify variables that are not specified in platform_path_map""" + site = Site([config_root / "site_main.json"]) + out = site.platform_path_key("{unset-variable}/is/not/modified") + assert out.as_posix() == "{unset-variable}/is/not/modified" class TestPlatformPathMapDict: @@ -652,3 +739,15 @@ def test_site_finalize(self, config_root): config_root / "site" / "eps" / "site_finalize.json", ] ) + + def test_habcache_cls(self, config_root, uncached_resolver): + """Test that site entry_point `hab.habcache_cls` is respected.""" + # By default `hab.cache.Cache` class is used + assert isinstance(uncached_resolver.site.cache, Cache) + + # The `hab.habcache_cls` entry_point uses the requested class + with pytest.raises( + NotImplementedError, + match="hab_test_entry_points.CacheVX class was used", + ): + Site([config_root / "site" / "eps" / "site_habcache_cls.json"]) diff --git a/tests/test_solver.py b/tests/test_solver.py index ac30b55..0ed5f86 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -62,14 +62,14 @@ def test_simplify_requirements(helpers, value, check): ), ), ) -def test_invalid_requirement_errors(resolver, requirements, match): +def test_invalid_requirement_errors(uncached_resolver, requirements, match): """Test that the correct error is raised if an invalid or missing requirement is specified.""" with pytest.raises(InvalidRequirementError, match=match): - resolver.resolve_requirements(requirements) + uncached_resolver.resolve_requirements(requirements) -def test_solver_errors(resolver): +def test_solver_errors(uncached_resolver): """Test that the correct errors are raised""" # Check that if we exceed max_redirects a MaxRedirectError is raised @@ -83,7 +83,7 @@ def test_solver_errors(resolver): ) ) - solver = Solver(requirements, resolver) + solver = Solver(requirements, uncached_resolver) solver.max_redirects = 0 with pytest.raises(MaxRedirectError, match="Redirect limit of 0 reached"): solver.resolve() diff --git a/tests/test_user_prefs.py b/tests/test_user_prefs.py index a3f43ad..9e9a2ec 100644 --- a/tests/test_user_prefs.py +++ b/tests/test_user_prefs.py @@ -54,8 +54,8 @@ def test_configure_logging(monkeypatch, tmpdir): assert logger.level == 30 -def test_filename(resolver, tmpdir): - prefs = resolver.user_prefs(load=True) +def test_filename(uncached_resolver, tmpdir): + prefs = uncached_resolver.user_prefs(load=True) # Defaults to Platform path assert prefs.filename == utils.Platform.user_prefs_filename() @@ -67,10 +67,10 @@ def test_filename(resolver, tmpdir): assert prefs.load() is False -def test_enabled_no_default(resolver): +def test_enabled_no_default(uncached_resolver): """If prefs_default is not specified default to disabled.""" - resolver.site.pop("prefs_default", None) - prefs = user_prefs.UserPrefs(resolver) + uncached_resolver.site.pop("prefs_default", None) + prefs = user_prefs.UserPrefs(uncached_resolver) assert prefs.enabled is False @@ -93,18 +93,18 @@ def test_enabled_no_default(resolver): ([False], False, False, False), ), ) -def test_enabled(resolver, setting, default, value, check): - resolver.site["prefs_default"] = setting - prefs = user_prefs.UserPrefs(resolver) +def test_enabled(uncached_resolver, setting, default, value, check): + uncached_resolver.site["prefs_default"] = setting + prefs = user_prefs.UserPrefs(uncached_resolver) assert prefs.enabled == default prefs.enabled = value assert prefs.enabled == check -def test_timeout(resolver): - resolver.site.pop("prefs_uri_timeout", None) - prefs = user_prefs.UserPrefs(resolver) +def test_timeout(uncached_resolver): + uncached_resolver.site.pop("prefs_uri_timeout", None) + prefs = user_prefs.UserPrefs(uncached_resolver) def set_uri_last_changed(**kwargs): d = datetime.datetime.today() @@ -120,21 +120,21 @@ def set_uri_last_changed(**kwargs): assert prefs.uri_timeout is None # prefs_uri_timeout enables uri timeout - resolver.site["prefs_uri_timeout"] = dict(minutes=5) + uncached_resolver.site["prefs_uri_timeout"] = dict(minutes=5) assert prefs.uri_timeout == datetime.timedelta(minutes=5) assert prefs.uri_is_timedout is False set_uri_last_changed(minutes=6) assert prefs.uri_is_timedout is True - resolver.site["prefs_uri_timeout"] = dict(days=30) + uncached_resolver.site["prefs_uri_timeout"] = dict(days=30) assert prefs.uri_timeout == datetime.timedelta(days=30) assert prefs.uri_is_timedout is False set_uri_last_changed(days=40) assert prefs.uri_is_timedout is True -def test_uri(resolver, tmpdir, monkeypatch): - resolver.site.pop("prefs_uri_timeout", None) +def test_uri(uncached_resolver, tmpdir, monkeypatch): + uncached_resolver.site.pop("prefs_uri_timeout", None) # Force the prefs to be saved into the test directory. if utils.Platform.name() == "windows": @@ -142,18 +142,18 @@ def test_uri(resolver, tmpdir, monkeypatch): else: monkeypatch.setenv("HOME", str(tmpdir)) # Ensure we are using the modified filepath - prefs_a = user_prefs.UserPrefs(resolver) + prefs_a = user_prefs.UserPrefs(uncached_resolver) assert prefs_a.filename.parent == tmpdir # No preferences are saved - prefs_b = user_prefs.UserPrefs(resolver) + prefs_b = user_prefs.UserPrefs(uncached_resolver) prefs_b._enabled = False assert prefs_b.uri is None # Preferences store an uri prefs_b["uri"] = "app/aliased" prefs_b.save() - prefs_c = user_prefs.UserPrefs(resolver) + prefs_c = user_prefs.UserPrefs(uncached_resolver) # Prefs are disabled prefs_c._enabled = False assert prefs_c.uri is None @@ -167,17 +167,17 @@ def test_uri(resolver, tmpdir, monkeypatch): prefs_c["uri_last_changed"] = last.isoformat() prefs_c.save() - prefs_d = user_prefs.UserPrefs(resolver) + prefs_d = user_prefs.UserPrefs(uncached_resolver) prefs_d._enabled = True # Timeout has not expired - resolver.site["prefs_uri_timeout"] = dict(hours=2) + uncached_resolver.site["prefs_uri_timeout"] = dict(hours=2) assert prefs_d.uri_check().timedout is False # Timeout has expired - resolver.site["prefs_uri_timeout"] = dict(minutes=5) + uncached_resolver.site["prefs_uri_timeout"] = dict(minutes=5) assert prefs_d.uri_check().timedout is True # Check that uri.setter is processed correctly - prefs_e = user_prefs.UserPrefs(resolver) + prefs_e = user_prefs.UserPrefs(uncached_resolver) # uri.setter is ignored if prefs are disabled prefs_e._enabled = False @@ -217,7 +217,7 @@ def test_uri(resolver, tmpdir, monkeypatch): '{\n "uri": "app', ), ) -def test_corruption(resolver, tmpdir, monkeypatch, caplog, test_text): +def test_corruption(uncached_resolver, tmpdir, monkeypatch, caplog, test_text): """Check how UserPrefs handles trying to load an incomplete or empty existing json document. """ @@ -242,7 +242,7 @@ def assert_log_exists(level, msg): else: monkeypatch.setenv("HOME", str(tmpdir)) # Ensure we are using the modified filepath - prefs = user_prefs.UserPrefs(resolver) + prefs = user_prefs.UserPrefs(uncached_resolver) assert prefs.filename.parent == tmpdir prefs_file = tmpdir / ".hab_user_prefs.json" @@ -255,7 +255,7 @@ def assert_log_exists(level, msg): # Check that the expected log messages are emitted when invalid # json file contents are encountered caplog.clear() - prefs = user_prefs.UserPrefs(resolver) + prefs = user_prefs.UserPrefs(uncached_resolver) # Even with invalid contents True will be returned assert prefs.load() # When corrupt prefs are encountered, default empty dict results diff --git a/tox.ini b/tox.ini index 0a8996d..f8a791c 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,9 @@ skipsdist = True [testenv] changedir = {toxinidir} skip_install = True -passenv = GITHUB_ACTIONS +passenv = + GITHUB_ACTIONS + HAB_TEST_UNCACHED_ONLY deps = -rrequirements.txt covdefaults