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
17 changes: 12 additions & 5 deletions integration-tests/.pytest.ini
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
[pytest]
addopts =
--capture=no
--code-highlight=yes
--color=yes
-rA
--show-capture=no
--strict-config
--strict-markers
--verbose
--tb=short

env =
D:CLP_BUILD_DIR=../build
D:CLP_CORE_BINS_DIR=../build/core
D:CLP_PACKAGE_DIR=../build/clp-package

log_cli = True
log_cli_date_format = %Y-%m-%d %H:%M:%S,%f
log_cli_format = %(name)s %(asctime)s [%(levelname)s] %(message)s
log_cli_level = INFO
log_cli_format = %(asctime)s.%(msecs)03d %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S

log_file_mode = a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of appending to the same file, can isolate the logs from different test runs instead? e.g., use set_log_path with the default log_file_mode = w. see the test code at pytest-dev/pytest#4752 for example

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this isn't here, the test output file will contain all of the pytest logging messages followed by all of the output from the various subprocesses called during the test run, like this:

INFO 2025-01-06 event1
INFO 2025-01-06 event2
INFO 2025-01-06 event3

<output following event 1>
<output following event 2>
<output following event 3>

It seems better to me to have everything written to the log file in order, so that it looks like this:

INFO 2025-01-06 event1
<output following event 1>
INFO 2025-01-06 event2
<output following event 2>
INFO 2025-01-06 event3
<output following event 3>

The logs from different test runs are already isolated by the code in conftest.py.

log_file_level = DEBUG
log_file_format = %(asctime)s.%(msecs)03d %(levelname)s [%(name)s:%(filename)s:%(lineno)d]: %(message)s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuing from some of our offline discussion - this format is not consistent the loggers config used by the rest of the project. that said, i'm fine keeping whatever's been proposed like you said it improves readability in the consoles.

in the long run, we should add infra for test CIs, where we should allow picking a JSON logger / some format string that's compatible with GH actions' problem matchers https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

log_file_date_format = %Y-%m-%d %H:%M:%S

markers =
clp: mark tests that use the CLP storage engine
clp_json: mark tests that use the clp-json package
Expand Down
3 changes: 3 additions & 0 deletions integration-tests/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ show_error_end = true
[tool.ruff]
extend = "../tools/yscope-dev-utils/exports/lint-configs/python/ruff.toml"

[tool.ruff.lint]
ignore = ["TRY400"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we avoid this global override and if really needed use inline # noqa: TRY400 instead? btw, where do we need such suppressions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TRY400 forces you to use logger.exception() instead of logger.error() inside an exception handler. I wanted to avoid using logger.exception() because it prints the whole traceback in the log message, and it looks cluttered (not to mention the exception is printed in the testing summary). There are four lines in the code that raise TRY400; should I use # noqa: TRY400 or keep it as is?


[tool.uv]
default-groups = ["clp", "dev"]

Expand Down
65 changes: 64 additions & 1 deletion integration-tests/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
"""Global pytest setup."""

import datetime
from collections.abc import Iterator
from pathlib import Path

import pytest

from tests.utils.logging_utils import BLUE, BOLD, RESET
from tests.utils.utils import resolve_path_env_var

# Make the fixtures defined in `tests/fixtures/` globally available without imports.
pytest_plugins = [
"tests.fixtures.integration_test_logs",
Expand All @@ -11,9 +18,10 @@
]


@pytest.hookimpl()
def pytest_addoption(parser: pytest.Parser) -> None:
"""
Adds options for pytest.
Adds options for `pytest`.

:param parser:
"""
Expand All @@ -23,3 +31,58 @@ def pytest_addoption(parser: pytest.Parser) -> None:
default="55000",
help="Base port for CLP package integration tests.",
)

# Sets up a unique log file for this test run, and stores the path to the file.
now = datetime.datetime.now() # noqa: DTZ005
test_run_id = now.strftime("%Y-%m-%d-%H-%M-%S")
log_file_path = (
resolve_path_env_var("CLP_BUILD_DIR")
/ "integration-tests"
/ "test_logs"
/ f"testrun_{test_run_id}.log"
)
parser.addini(
"log_file_path",
help="Path to the log file for this test.",
type="string",
default=str(log_file_path),
)


def pytest_itemcollected(item: pytest.Item) -> None:
"""
Prettifies the name of the test for output purposes.

:param item:
"""
item._nodeid = f"{BOLD}{BLUE}{item.nodeid}{RESET}" # noqa: SLF001


@pytest.hookimpl(tryfirst=True)
def pytest_report_header(config: pytest.Config) -> str:
"""
Adds a field to the header at the start of the test run that reports the path to the log file
for this test run.

:param config:
"""
log_file_path = Path(config.getini("log_file_path")).expanduser().resolve()
return f"Log file path for this test run: {log_file_path}"


@pytest.hookimpl(wrapper=True)
def pytest_runtest_setup(item: pytest.Item) -> Iterator[None]:
"""
Sets `log_file_path` as the output file for the logger.

:param item:
"""
config = item.config
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
if logging_plugin is None:
err_msg = "Expected pytest plugin 'logging-plugin' to be registered."
raise RuntimeError(err_msg)

log_file_path = Path(config.getini("log_file_path"))
logging_plugin.set_log_path(str(log_file_path))
yield
25 changes: 13 additions & 12 deletions integration-tests/tests/fixtures/package_instance.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Fixtures that start and stop CLP package instances for integration tests."""

import logging
from collections.abc import Iterator

import pytest
Expand All @@ -13,9 +14,13 @@
stop_clp_package,
)

logger = logging.getLogger(__name__)


@pytest.fixture(scope="module")
def fixt_package_instance(fixt_package_test_config: PackageTestConfig) -> Iterator[PackageInstance]:
def fixt_package_instance(
request: pytest.FixtureRequest, fixt_package_test_config: PackageTestConfig
) -> Iterator[PackageInstance]:
"""
Starts a CLP package instance for the given configuration and stops it during teardown.

Expand All @@ -25,18 +30,14 @@ def fixt_package_instance(fixt_package_test_config: PackageTestConfig) -> Iterat
:param fixt_package_test_config:
:return: Iterator that yields the running package instance.
"""
mode_config = fixt_package_test_config.mode_config
mode_name = mode_config.mode_name

try:
start_clp_package(fixt_package_test_config)
logger.info("Starting the '%s' package...", mode_name)
start_clp_package(request, fixt_package_test_config)
instance = PackageInstance(package_test_config=fixt_package_test_config)
yield instance
except RuntimeError:
mode_config = fixt_package_test_config.mode_config
mode_name = mode_config.mode_name
base_port = fixt_package_test_config.base_port
pytest.fail(
f"Failed to start the {mode_name} package. This could mean that one of the ports"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe i wasn't reading carefully - why we removed this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because start_clp_package never raises a RuntimeError (run_and_assert raises a SubprocessError). I can move the bit about the ports to the section that catches the SubprocessError in start_clp_package, but with the full output always being captured in the log file, I think that it will be more helpful to just tell the dev to check the test log (as we are now doing in this PR).

f" derived from base_port={base_port} was unavailable. You can specify a new value for"
" base_port with the '--base-port' flag."
)
finally:
stop_clp_package(fixt_package_test_config)
logger.info("Stopping the '%s' package...", mode_name)
stop_clp_package(request, fixt_package_test_config)
10 changes: 10 additions & 0 deletions integration-tests/tests/fixtures/package_test_config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Fixtures that create and remove temporary config files for CLP packages."""

import logging
from collections.abc import Iterator

import pytest

from tests.utils.config import PackageModeConfig, PackagePathConfig, PackageTestConfig
from tests.utils.logging_utils import construct_log_err_msg
from tests.utils.port_utils import assign_ports_from_base

logger = logging.getLogger(__name__)


@pytest.fixture(scope="module")
def fixt_package_test_config(
Expand All @@ -22,14 +26,19 @@ def fixt_package_test_config(
:raise ValueError: if the CLP base port's value is invalid.
"""
mode_config: PackageModeConfig = request.param
mode_name = mode_config.mode_name
clp_config_obj = mode_config.clp_config

logger.info("Setting up the '%s' package...", mode_name)

# Assign ports based on the clp base port CLI option.
logger.debug("Assigning ports to the components in the '%s' package...", mode_name)
base_port_string = request.config.getoption("--base-port")
try:
base_port = int(base_port_string)
except ValueError as err:
err_msg = f"Invalid value '{base_port_string}' for '--base-port'; expected an integer."
logger.error(construct_log_err_msg(err_msg))
raise ValueError(err_msg) from err
assign_ports_from_base(base_port, clp_config_obj)

Expand All @@ -43,4 +52,5 @@ def fixt_package_test_config(
try:
yield package_test_config
finally:
logger.info("Cleaning up the '%s' package...", mode_name)
package_test_config.temp_config_file_path.unlink(missing_ok=True)
24 changes: 6 additions & 18 deletions integration-tests/tests/package_tests/clp_json/test_clp_json.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Tests for the clp-json package."""

import logging

import pytest
from clp_py_utils.clp_config import (
ClpConfig,
Expand All @@ -16,9 +14,6 @@
from tests.utils.clp_mode_utils import CLP_API_SERVER_COMPONENT, CLP_BASE_COMPONENTS
from tests.utils.config import PackageInstance, PackageModeConfig

logger = logging.getLogger(__name__)


# Mode description for this module.
CLP_JSON_MODE = PackageModeConfig(
mode_name="clp-json",
Expand All @@ -36,27 +31,26 @@
pytestmark = [
pytest.mark.package,
pytest.mark.clp_json,
pytest.mark.parametrize("fixt_package_test_config", [CLP_JSON_MODE], indirect=True),
pytest.mark.parametrize(
"fixt_package_test_config", [CLP_JSON_MODE], indirect=True, ids=[CLP_JSON_MODE.mode_name]
),
]


@pytest.mark.startup
def test_clp_json_startup(fixt_package_instance: PackageInstance) -> None:
"""
Validate that the `clp-json` package starts up successfully.
Validates that the `clp-json` package starts up successfully.

:param fixt_package_instance:
"""
validate_package_instance(fixt_package_instance)

log_msg = "test_clp_json_startup was successful."
logger.info(log_msg)


@pytest.mark.compression
def test_clp_json_compression(fixt_package_instance: PackageInstance) -> None:
"""
Validate that the `clp-json` package successfully compresses some dataset.
Validates that the `clp-json` package successfully compresses some dataset.

:param fixt_package_instance:
"""
Expand All @@ -65,14 +59,11 @@ def test_clp_json_compression(fixt_package_instance: PackageInstance) -> None:
# TODO: compress some dataset and check the correctness of compression.
assert True

log_msg = "test_clp_json_compression was successful."
logger.info(log_msg)


@pytest.mark.search
def test_clp_json_search(fixt_package_instance: PackageInstance) -> None:
"""
Validate that the `clp-json` package successfully searches some dataset.
Validates that the `clp-json` package successfully searches some dataset.

:param fixt_package_instance:
"""
Expand All @@ -83,6 +74,3 @@ def test_clp_json_search(fixt_package_instance: PackageInstance) -> None:
# TODO: search through that dataset and check the correctness of the search results.

assert True

log_msg = "test_clp_json_search was successful."
logger.info(log_msg)
18 changes: 8 additions & 10 deletions integration-tests/tests/package_tests/clp_text/test_clp_text.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Tests for the clp-text package."""

import logging

import pytest
from clp_py_utils.clp_config import (
ClpConfig,
Expand All @@ -16,9 +14,6 @@
from tests.utils.clp_mode_utils import CLP_BASE_COMPONENTS
from tests.utils.config import PackageInstance, PackageModeConfig

logger = logging.getLogger(__name__)


# Mode description for this module.
CLP_TEXT_MODE = PackageModeConfig(
mode_name="clp-text",
Expand All @@ -38,14 +33,17 @@
pytestmark = [
pytest.mark.package,
pytest.mark.clp_text,
pytest.mark.parametrize("fixt_package_test_config", [CLP_TEXT_MODE], indirect=True),
pytest.mark.parametrize(
"fixt_package_test_config", [CLP_TEXT_MODE], indirect=True, ids=[CLP_TEXT_MODE.mode_name]
),
]


@pytest.mark.startup
def test_clp_text_startup(fixt_package_instance: PackageInstance) -> None:
"""Tests package startup."""
validate_package_instance(fixt_package_instance)
"""
Validates that the `clp-text` package starts up successfully.

log_msg = "test_clp_text_startup was successful."
logger.info(log_msg)
:param fixt_package_instance:
"""
validate_package_instance(fixt_package_instance)
16 changes: 9 additions & 7 deletions integration-tests/tests/test_identity_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from tests.utils.asserting_utils import run_and_assert
from tests.utils.asserting_utils import run_and_log_to_file
from tests.utils.config import (
ClpCorePathConfig,
CompressionTestPathConfig,
Expand Down Expand Up @@ -73,10 +73,10 @@ def test_clp_identity_transform(
src_path,
]
# fmt: on
run_and_assert(compression_cmd)
run_and_log_to_file(request, compression_cmd)

decompression_cmd = [bin_path, "x", compression_path, decompression_path]
run_and_assert(decompression_cmd)
run_and_log_to_file(request, decompression_cmd)

input_path = test_paths.logs_source_dir
output_path = test_paths.decompression_dir
Expand Down Expand Up @@ -113,7 +113,7 @@ def test_clp_s_identity_transform(
logs_source_dir=integration_test_logs.extraction_dir,
integration_test_path_config=integration_test_path_config,
)
_clp_s_compress_and_decompress(clp_core_path_config, test_paths)
_clp_s_compress_and_decompress(request, clp_core_path_config, test_paths)

# Recompress the decompressed output that's consolidated into a single json file, and decompress
# it again to verify consistency. The compression input of the second iteration points to the
Expand All @@ -126,7 +126,7 @@ def test_clp_s_identity_transform(
logs_source_dir=test_paths.decompression_dir,
integration_test_path_config=integration_test_path_config,
)
_clp_s_compress_and_decompress(clp_core_path_config, consolidated_json_test_paths)
_clp_s_compress_and_decompress(request, clp_core_path_config, consolidated_json_test_paths)

_consolidated_json_file_name = "original"
input_path = consolidated_json_test_paths.logs_source_dir / _consolidated_json_file_name
Expand All @@ -140,6 +140,7 @@ def test_clp_s_identity_transform(


def _clp_s_compress_and_decompress(
request: pytest.FixtureRequest,
clp_core_path_config: ClpCorePathConfig,
test_paths: CompressionTestPathConfig,
) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just found this guy does have a docstring. shall we add one or was it too trivial?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should probably have one; maybe once you approve of the overarching approach of this PR then I'll go through the rest of the code and change the logging/docstrings (I stuck to just the package testing code for now)

Expand All @@ -148,5 +149,6 @@ def _clp_s_compress_and_decompress(
src_path = str(test_paths.logs_source_dir)
compression_path = str(test_paths.compression_dir)
decompression_path = str(test_paths.decompression_dir)
run_and_assert([bin_path, "c", compression_path, src_path])
run_and_assert([bin_path, "x", compression_path, decompression_path])

run_and_log_to_file(request, [bin_path, "c", compression_path, src_path])
run_and_log_to_file(request, [bin_path, "x", compression_path, decompression_path])
Loading
Loading