Skip to content

Commit

Permalink
WIP: Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
MHendricks committed Jan 10, 2024
1 parent 757b688 commit 86ccf62
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 39 deletions.
6 changes: 6 additions & 0 deletions hab/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def cache(self, force=False):

return self._cache

def clear(self):
"""Reset the cache forcing it to reload the next time its used."""
if self._cache:
logger.debug("Site cache contents cleared")
self._cache = None

def config_paths(self, flat=False):
if flat:
return self.cache().get("flat", {}).get("config_paths", {})
Expand Down
1 change: 1 addition & 0 deletions hab/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def clear_caches(self):
logger.debug("Resolver cache cleared.")
self._configs = None
self._distros = None
self.site.cache.clear()

def closest_config(self, path, default=False):
"""Returns the most specific leaf or the tree root matching path. Ignoring any
Expand Down
19 changes: 14 additions & 5 deletions hab/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ def __init__(self, paths=None, platform=None):
def data(self):
return self.frozen_data.get(self.platform)

def dump(self, verbosity=0, color=None):
def dump(self, verbosity=0, color=None, width=80):
"""Return a string of the properties and their values.
Args:
verbosity (int, optional): More information is shown with higher values.
color (bool, optional): Add console colorization to output. If None,
respect the site property "colorize" defaulting to True.
width (int, optional): The desired width for wrapping. The output may
exceed this value, but it will attempt to respect it.
Returns:
str: The configuration converted to a string
Expand All @@ -85,7 +87,7 @@ def cached_fmt(path, cached):
cache_file = self.cache.site_cache_path(path)
path = cached_fmt(path, cache_file.is_file())
hab_paths.append(str(path))
site_ret = utils.dump_object({"HAB_PATHS": hab_paths}, color=color)
site_ret = utils.dump_object({"HAB_PATHS": hab_paths}, color=color, width=width)
# Include all of the resolved site configurations
ret = []
for prop, value in self.items():
Expand All @@ -96,15 +98,22 @@ def cached_fmt(path, cached):
prop, value, cache, include_path=False
):
paths.append(cached_fmt(dirname, cached))
txt = utils.dump_object(paths, label=f"{prop}: ", color=color)
txt = utils.dump_object(
paths, label=f"{prop}: ", color=color, width=width
)
elif verbosity < 1 and isinstance(value, dict):
# This is too complex for most site dumps, hide the details behind
# a higher verbosity setting.
txt = utils.dump_object(
f"Dictionary keys: {len(value)}", label=f"{prop}: ", color=color
f"Dictionary keys: {len(value)}",
label=f"{prop}: ",
color=color,
width=width,
)
else:
txt = utils.dump_object(value, label=f"{prop}: ", color=color)
txt = utils.dump_object(
value, label=f"{prop}: ", color=color, width=width
)

ret.append(txt)

Expand Down
57 changes: 57 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
from contextlib import contextmanager
from pathlib import Path, PurePath
Expand All @@ -13,6 +14,62 @@ def config_root():
return Path(__file__).parent


@pytest.fixture
def habcached_site_file(config_root, tmpdir):
"""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(tmpdir) / "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 tmpdir 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
def habcached_resolver(habcached_site_file):
"""Returns a Resolver using a habcache file.
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
"""
"""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 = 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.save_cache(resolver, habcached_site_file)
return resolver


@pytest.fixture
def resolver(config_root):
"""Return a standard testing resolver"""
Expand Down
14 changes: 14 additions & 0 deletions tests/distros/the_dcc/pre/.hab.json
Original file line number Diff line number Diff line change
@@ -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"]
]
}
}
3 changes: 3 additions & 0 deletions tests/distros/the_dcc/pre/README.md
Original file line number Diff line number Diff line change
@@ -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`.
72 changes: 43 additions & 29 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from pathlib import Path

from hab import Resolver, Site, utils
from hab import Resolver, Site
from hab.parsers import Config, DistroVersion


Expand Down Expand Up @@ -41,38 +40,53 @@ def test_site_cache_path(config_root, resolver, tmpdir):
assert site.cache.cache_template == ".{stem}.hab_cache"


def test_save_cache(config_root, tmpdir):
platform = utils.Platform.name()
site_file = Path(tmpdir) / "site.json"
site_src = config_root / "site_main.json"

# Copy the site_main.json file into the testing dir so we can
# test generating the cache file.
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
append["platform_path_maps"]["config-root"] = {platform: str(config_root)}

with site_file.open("w") as fle:
json.dump(data, fle, indent=4)

site = Site([site_file])
resolver = Resolver(site)
cache_file = site.cache.save_cache(resolver, site_file)

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 = cache_file.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, 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(resolver, key) == getattr(cached_resolver, key)


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)
12 changes: 12 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
70 changes: 65 additions & 5 deletions tests/test_site.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import sys
from pathlib import Path, PurePosixPath, PureWindowsPath

import colorama
import pytest
from colorama import Fore, Style

from hab import Resolver, Site, utils

Expand Down Expand Up @@ -258,10 +258,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)
Expand All @@ -272,6 +269,63 @@ 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,
}

# 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
Expand Down Expand Up @@ -386,6 +440,12 @@ def test_win(self, monkeypatch, config_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:
def assert_main(self, site):
Expand Down

0 comments on commit 86ccf62

Please sign in to comment.