diff --git a/metricflow/test/cli/test_cli.py b/metricflow/test/cli/test_cli.py index cc7a12f3c3..5f7056e99c 100644 --- a/metricflow/test/cli/test_cli.py +++ b/metricflow/test/cli/test_cli.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import shutil import textwrap from contextlib import contextmanager @@ -7,6 +8,7 @@ from typing import Iterator import pytest +from _pytest.fixtures import FixtureRequest from dbt_semantic_interfaces.parsing.dir_to_model import ( parse_yaml_files_to_validation_ready_semantic_manifest, ) @@ -25,7 +27,14 @@ ) from metricflow.protocols.sql_client import SqlEngine from metricflow.test.fixtures.cli_fixtures import MetricFlowCliRunner +from metricflow.test.fixtures.setup_fixtures import MetricFlowTestSessionState from metricflow.test.model.example_project_configuration import EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE +from metricflow.test.snapshot_utils import assert_object_snapshot_equal + +logger = logging.getLogger(__name__) + + +# TODO: Use snapshots to compare CLI output for all tests here. def test_query(capsys: pytest.CaptureFixture, cli_runner: MetricFlowCliRunner) -> None: # noqa: D @@ -144,3 +153,92 @@ def test_list_entities(capsys: pytest.CaptureFixture, cli_runner: MetricFlowCliR assert "guest" in resp.output assert "host" in resp.output + + +def test_saved_query( # noqa: D + request: FixtureRequest, + capsys: pytest.CaptureFixture, + mf_test_session_state: MetricFlowTestSessionState, + cli_runner: MetricFlowCliRunner, +) -> None: + # Disabling capsys to resolve error "ValueError: I/O operation on closed file". Better solution TBD. + with capsys.disabled(): + resp = cli_runner.run( + query, args=["--saved-query", "p0_booking", "--order", "metric_time__day,listing__capacity_latest"] + ) + + assert resp.exit_code == 0 + + assert_object_snapshot_equal( + request=request, mf_test_session_state=mf_test_session_state, obj_id="cli_output", obj=resp.output + ) + + +def test_saved_query_with_where( # noqa: D + request: FixtureRequest, + capsys: pytest.CaptureFixture, + mf_test_session_state: MetricFlowTestSessionState, + cli_runner: MetricFlowCliRunner, +) -> None: + # Disabling capsys to resolve error "ValueError: I/O operation on closed file". Better solution TBD. + with capsys.disabled(): + resp = cli_runner.run( + query, + args=[ + "--saved-query", + "p0_booking", + "--order", + "metric_time__day,listing__capacity_latest", + "--where", + "{{ Dimension('listing__capacity_latest') }} > 4", + ], + ) + + assert resp.exit_code == 0 + + assert_object_snapshot_equal( + request=request, mf_test_session_state=mf_test_session_state, obj_id="cli_output", obj=resp.output + ) + + +def test_saved_query_with_limit( # noqa: D + request: FixtureRequest, + capsys: pytest.CaptureFixture, + mf_test_session_state: MetricFlowTestSessionState, + cli_runner: MetricFlowCliRunner, +) -> None: + # Disabling capsys to resolve error "ValueError: I/O operation on closed file". Better solution TBD. + with capsys.disabled(): + resp = cli_runner.run( + query, + args=[ + "--saved-query", + "p0_booking", + "--order", + "metric_time__day,listing__capacity_latest", + "--limit", + "3", + ], + ) + + assert resp.exit_code == 0 + + assert_object_snapshot_equal( + request=request, mf_test_session_state=mf_test_session_state, obj_id="cli_output", obj=resp.output + ) + + +def test_saved_query_explain( # noqa: D + capsys: pytest.CaptureFixture, + mf_test_session_state: MetricFlowTestSessionState, + cli_runner: MetricFlowCliRunner, +) -> None: + # Disabling capsys to resolve error "ValueError: I/O operation on closed file". Better solution TBD. + with capsys.disabled(): + resp = cli_runner.run( + query, + args=["--explain", "--saved-query", "p0_booking", "--order", "metric_time__day,listing__capacity_latest"], + ) + + # Currently difficult to compare explain output due to randomly generated IDs. + assert resp.exit_code == 0 diff --git a/metricflow/test/fixtures/semantic_manifest_yamls/simple_manifest/saved_queries.yaml b/metricflow/test/fixtures/semantic_manifest_yamls/simple_manifest/saved_queries.yaml new file mode 100644 index 0000000000..0149eacd23 --- /dev/null +++ b/metricflow/test/fixtures/semantic_manifest_yamls/simple_manifest/saved_queries.yaml @@ -0,0 +1,12 @@ +--- +saved_query: + name: p0_booking + description: Booking-related metrics that are of the highest priority. + metrics: + - bookings + - instant_bookings + group_bys: + - TimeDimension('metric_time', 'day') + - Dimension('listing__capacity_latest') + where: + - "{{ Dimension('listing__capacity_latest') }} > 3" diff --git a/metricflow/test/snapshot_utils.py b/metricflow/test/snapshot_utils.py index ac4fcad18f..9d8b478d7c 100644 --- a/metricflow/test/snapshot_utils.py +++ b/metricflow/test/snapshot_utils.py @@ -167,14 +167,16 @@ def assert_snapshot_text_equal( if incomparable_strings_replacement_function is not None: snapshot_text = incomparable_strings_replacement_function(snapshot_text) + # Add a new line at the end of the file so that PRs don't show the "no newline" symbol on Github. + if len(snapshot_text) > 1 and snapshot_text[-1] != "\n": + snapshot_text = snapshot_text + "\n" + # If we are in overwrite mode, make a new plan: if mf_test_session_state.overwrite_snapshots: # Create parent directory for the plan text files. os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w") as snapshot_text_file: snapshot_text_file.write(snapshot_text) - # Add a new line at the end of the file so that PRSs don't show the "no newline" symbol on Github. - snapshot_text_file.write("\n") # Throw an exception if the plan is not there. if not os.path.exists(file_path): @@ -198,8 +200,7 @@ def assert_snapshot_text_equal( # Read the existing plan from the file and compare with the actual plan with open(file_path, "r") as snapshot_text_file: - # Remove the newline that was added from above. - expected_snapshot_text = snapshot_text_file.read().rstrip() + expected_snapshot_text = snapshot_text_file.read() if exclude_line_regex: # Filter out lines that should be ignored. diff --git a/metricflow/test/snapshots/test_cli.py/str/test_saved_query__cli_output.txt b/metricflow/test/snapshots/test_cli.py/str/test_saved_query__cli_output.txt new file mode 100644 index 0000000000..534002ae25 --- /dev/null +++ b/metricflow/test/snapshots/test_cli.py/str/test_saved_query__cli_output.txt @@ -0,0 +1,11 @@ +| metric_time__day | listing__capacity_latest | bookings | instant_bookings | +|:--------------------|---------------------------:|-----------:|-------------------:| +| 2019-12-01 00:00:00 | 5.00 | 1.00 | 0.00 | +| 2019-12-18 00:00:00 | 4.00 | 4.00 | 2.00 | +| 2019-12-19 00:00:00 | 4.00 | 6.00 | 6.00 | +| 2019-12-19 00:00:00 | 5.00 | 2.00 | 0.00 | +| 2019-12-20 00:00:00 | 5.00 | 2.00 | 0.00 | +| 2020-01-01 00:00:00 | 4.00 | 2.00 | 1.00 | +| 2020-01-02 00:00:00 | 4.00 | 3.00 | 3.00 | +| 2020-01-02 00:00:00 | 5.00 | 1.00 | 0.00 | +| 2020-01-03 00:00:00 | 5.00 | 1.00 | 0.00 | diff --git a/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_limit__cli_output.txt b/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_limit__cli_output.txt new file mode 100644 index 0000000000..d41d9d9570 --- /dev/null +++ b/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_limit__cli_output.txt @@ -0,0 +1,5 @@ +| metric_time__day | listing__capacity_latest | bookings | instant_bookings | +|:--------------------|---------------------------:|-----------:|-------------------:| +| 2019-12-01 00:00:00 | 5.00 | 1.00 | 0.00 | +| 2019-12-18 00:00:00 | 4.00 | 4.00 | 2.00 | +| 2019-12-19 00:00:00 | 4.00 | 6.00 | 6.00 | diff --git a/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_where__cli_output.txt b/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_where__cli_output.txt new file mode 100644 index 0000000000..4e10da7e3d --- /dev/null +++ b/metricflow/test/snapshots/test_cli.py/str/test_saved_query_with_where__cli_output.txt @@ -0,0 +1,7 @@ +| metric_time__day | listing__capacity_latest | bookings | instant_bookings | +|:--------------------|---------------------------:|-----------:|-------------------:| +| 2019-12-01 00:00:00 | 5.00 | 1.00 | 0.00 | +| 2019-12-19 00:00:00 | 5.00 | 2.00 | 0.00 | +| 2019-12-20 00:00:00 | 5.00 | 2.00 | 0.00 | +| 2020-01-02 00:00:00 | 5.00 | 1.00 | 0.00 | +| 2020-01-03 00:00:00 | 5.00 | 1.00 | 0.00 |