Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-metricflow-unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,5 @@ jobs:
}

- name: Run Package-Build Tests
shell: bash
run: "make test-build-packages"
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
metricflow==0.209.0
# Using the root so tests in `dbt-metricflow` run using the current code in `metricflow`.
# This ensures that breaking changes are resolved in the PR that makes the breaking change.
# This should be updated to the correct version before release.
metricflow @ {root:parent:uri}
39 changes: 39 additions & 0 deletions scripts/ci_tests/dbt_metricflow_package_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

import subprocess
import textwrap
from pathlib import Path
from typing import Optional


def _run_shell_command(command: str, cwd: Optional[Path] = None) -> None:
if cwd is None:
cwd = Path.cwd()

print(
textwrap.dedent(
f"""\
Running via shell:
command: {command!r}
cwd: {cwd.as_posix()!r}
"""
).rstrip()
)
subprocess.check_call(command, shell=True, cwd=cwd.as_posix())


if __name__ == "__main__":
# Check that the `mf` command is installed.
_run_shell_command("which python")
_run_shell_command("which mf")
# Run the tutorial using `--yes` to create the sample project without user interaction.
_run_shell_command("mf tutorial --yes")
tutorial_directory = Path.cwd().joinpath("mf_tutorial_project")

# Run the first few tutorial steps.
_run_shell_command("dbt seed", cwd=tutorial_directory)
_run_shell_command("dbt build", cwd=tutorial_directory)
_run_shell_command(
"mf query --metrics transactions --group-by metric_time --order metric_time",
cwd=tutorial_directory,
)
101 changes: 73 additions & 28 deletions scripts/ci_tests/run_package_build_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,94 @@
import logging
import tempfile
import venv
from collections.abc import Sequence
from pathlib import Path

from scripts.mf_script_helper import MetricFlowScriptHelper

logger = logging.getLogger(__name__)


def _run_package_build_test(package_directory: Path, package_test_script: Path) -> None:
"""Run a test to verify that a package is built properly.
def _run_package_test(
package_directory: Path,
package_test_script: Path,
build_wheel: bool,
optional_package_dependencies_to_install: Sequence[str] = (),
) -> None:
"""Run tests to verify a package.

Given the directory where the package is located, this will build the package using `hatch build` and install the
created Python-wheel files into a clean virtual environment. Finally, the given test script will be run using the
virtual environment.
Given the directory where the package is located, install the package by using a wheel built from the package or an
use editable installation. Once installed, run the package test script.

Args:
package_directory: Root directory where the package is located.
package_test_script: The path to the script that should be run.

optional_package_dependencies_to_install: If the given package defines optional dependencies that can be
installed, install these. e.g. for `dbt-metricflow[dbt-duckdb]`, specify `dbt-duckdb`.
build_wheel: If set, build a wheel from the package and install the wheel in the virtual environment. Otherwise,
use an editable installation.
Returns: None
Raises: Exception on test failure.
"""
logger.info(f"Running package build test for {str(package_directory)!r} using {str(package_test_script)!r}")
package_directory_str = package_directory.as_posix()
package_test_script_str = package_test_script.as_posix()
logger.info(f"Running package build test for {package_directory_str!r} using {package_test_script_str!r}")

try:
with tempfile.TemporaryDirectory() as temporary_directory_str:
temporary_directory = Path(temporary_directory_str)
venv_directory = temporary_directory.joinpath("venv")
logger.info(f"Creating venv at {str(venv_directory)!r}")
logger.info(f"Creating a new venv at {venv_directory.as_posix()!r}")

venv.create(venv_directory, with_pip=True)
pip_executable = Path(venv_directory, "bin/pip")
python_executable = Path(venv_directory, "bin/python")

logger.info(f"Building package at {str(package_directory)!r}")
logger.info(f"Running package build test for {str(package_directory)!r} using {str(package_test_script)!r}")
MetricFlowScriptHelper.run_command(["hatch", "build"], working_directory=package_directory)

logger.info("Installing package using generated wheels")
MetricFlowScriptHelper.run_shell_command(f'{pip_executable} install "{str(package_directory)}"/dist/*.whl')

logger.info("Running test using installed package in venv")
MetricFlowScriptHelper.run_command(
[str(python_executable), str(package_test_script)], working_directory=temporary_directory
pip_executable = Path(venv_directory, "bin/pip").as_posix()

logger.info(f"Using package at {package_directory_str!r}")

if build_wheel:
MetricFlowScriptHelper.run_command(["hatch", "clean"], working_directory=package_directory)
MetricFlowScriptHelper.run_command(["hatch", "build"], working_directory=package_directory)

logger.info("Installing package in venv using generated wheels")
paths_to_wheels = _get_wheels_in_directory(package_directory.joinpath("dist"))
if len(paths_to_wheels) != 1:
raise RuntimeError(f"Expected exactly one wheel but got {paths_to_wheels}")

path_to_wheel_str = str(paths_to_wheels[0])
MetricFlowScriptHelper.run_command([pip_executable, "install", path_to_wheel_str])
for optional_package_dependency in optional_package_dependencies_to_install:
MetricFlowScriptHelper.run_command(
[pip_executable, "install", f"{path_to_wheel_str}[{optional_package_dependency}]"]
)
else:
MetricFlowScriptHelper.run_command([pip_executable, "install", "-e", package_directory_str])
for optional_package_dependency in optional_package_dependencies_to_install:
MetricFlowScriptHelper.run_command(
[pip_executable, "install", f"{package_directory_str}[{optional_package_dependency}]"]
)

logger.info("Running test using venv")
venv_activate = venv_directory.joinpath("bin", "activate").as_posix()
MetricFlowScriptHelper.run_shell_command(
# Using period instead of `source` for compatibility with `sh`.
f"cd {temporary_directory_str} && . {venv_activate} && python {package_test_script_str}",
)

logger.info(f"Test passed {str(package_test_script)!r}")
logger.info(f"Test passed {package_test_script_str!r}")
except Exception as e:
raise PackageBuildTestFailureException(
f"Package build test failed for {str(package_directory)!r} using {str(package_test_script)!r}"
f"Package test failed for {package_directory_str!r} using {package_test_script_str!r}"
) from e


def _get_wheels_in_directory(directory: Path) -> Sequence[Path]:
paths_to_wheels = []
for path_item in directory.iterdir():
if path_item.is_file() and path_item.suffix == ".whl":
paths_to_wheels.append(path_item)
return paths_to_wheels


class PackageBuildTestFailureException(Exception): # noqa: D101
pass

Expand All @@ -74,16 +111,24 @@ class PackageBuildTestFailureException(Exception): # noqa: D101

logger.info(f"Using {metricflow_repo_directory=}")

# Test building the `metricflow` package.
_run_package_build_test(
# Test the `metricflow` package.
_run_package_test(
package_directory=metricflow_repo_directory,
package_test_script=metricflow_repo_directory.joinpath("scripts/ci_tests/metricflow_package_test.py"),
build_wheel=True,
)

# Test building `metricflow-semantics` package.
_run_package_build_test(
# Test the `metricflow-semantics` package.
_run_package_test(
package_directory=metricflow_repo_directory.joinpath("metricflow-semantics"),
package_test_script=metricflow_repo_directory.joinpath("scripts/ci_tests/metricflow_semantics_package_test.py"),
build_wheel=True,
)

# Add entry for `dbt-metricflow` once build issues are resolved.
# Test the `dbt-metricflow` package.
_run_package_test(
package_directory=metricflow_repo_directory.joinpath("dbt-metricflow"),
package_test_script=metricflow_repo_directory.joinpath("scripts/ci_tests/dbt_metricflow_package_test.py"),
optional_package_dependencies_to_install=("dbt-duckdb",),
build_wheel=False,
)