Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
0678f96
Accept the terms of service for conda repos
bpkroth Jul 17, 2025
a3a82f7
Make conda output easier for debugging
bpkroth Jul 17, 2025
fcbfc49
more output adjustments
bpkroth Jul 17, 2025
c666915
Revert "more output adjustments"
bpkroth Jul 17, 2025
2dcfa42
Revert "Make conda output easier for debugging"
bpkroth Jul 17, 2025
9965602
try a suggestion
bpkroth Jul 17, 2025
57b673a
fixups
bpkroth Jul 17, 2025
2084a08
Merge branch 'main' into ci-fixups
bpkroth Jul 17, 2025
ecebe33
type fixups
bpkroth Sep 18, 2025
e7f524d
type checking fixups
bpkroth Sep 18, 2025
1b490ae
use conda by default
bpkroth Sep 18, 2025
9ad7794
small pyright fixups
bpkroth Sep 22, 2025
3c0aa23
Install pre-built pyrfr from conda to workaround build error.
bpkroth Sep 22, 2025
a8d9ccd
ignore a deprecation warning in matplotlib
bpkroth Sep 22, 2025
1862037
fixups
bpkroth Sep 22, 2025
b2530a3
ignore more warnings
bpkroth Sep 22, 2025
d914bcc
Avoid Debian trixie for now since there's no azure-cli package yet. …
bpkroth Sep 22, 2025
b6b5561
Moby has also been removed from Debian Trixie
bpkroth Sep 22, 2025
b059ac3
add more debug logging for missing docker support
bpkroth Sep 23, 2025
021f451
more debugging
bpkroth Sep 23, 2025
ac57951
comments
bpkroth Sep 23, 2025
4462cdf
fixup for local testing
bpkroth Sep 23, 2025
74d94cf
log docker missing warnings
bpkroth Sep 23, 2025
06a9826
Split docker test fixtures out so they can come up and down in parallel.
bpkroth Sep 23, 2025
cba92cd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2025
797ac40
comments and revert to moby
bpkroth Sep 23, 2025
07924d6
comments and revert to moby
bpkroth Sep 23, 2025
9870d35
make some more fixtures available
bpkroth Sep 23, 2025
c775d89
quotes fixup
bpkroth Sep 23, 2025
72aec7b
upload the coverage.xml file regardless
bpkroth Sep 23, 2025
9fd6843
comments and port forwarding for doc viewing
bpkroth Sep 23, 2025
95a4ae0
Merge branch 'ci-fixups' into split-docker-tests
bpkroth Sep 23, 2025
6fbd25b
revert
bpkroth Sep 23, 2025
382ab78
revertme: temporarily make docker required to see what the issue is i…
bpkroth Sep 23, 2025
496fba8
more debugging
bpkroth Sep 24, 2025
6298fa4
more debug info
bpkroth Oct 21, 2025
2f7c010
trying to print some extra info about the docker env while in the git…
bpkroth Oct 21, 2025
06d4d8d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2025
3621506
more debugging
bpkroth Oct 21, 2025
751028e
more debugging
bpkroth Oct 21, 2025
d4fc3ba
mypy
bpkroth Oct 21, 2025
5ef8132
fix debugging output
bpkroth Oct 21, 2025
74c30f8
add some more debug info
bpkroth Oct 21, 2025
4b090c6
improved debug checks
bpkroth Oct 21, 2025
fd61c79
cleanup
bpkroth Oct 21, 2025
5b28210
cleanup
bpkroth Oct 21, 2025
2c8668f
cleanup
bpkroth Oct 21, 2025
5088cae
comments and sync
bpkroth Oct 21, 2025
f1ca2d0
Merge branch 'ci-fixups' into split-docker-tests
bpkroth Oct 21, 2025
bee6f99
lint fixups
bpkroth Oct 21, 2025
d6107ad
Merge branch 'main' into split-docker-tests
bpkroth Oct 22, 2025
ba46ff6
Apply suggestion from @Copilot
bpkroth Oct 22, 2025
30f87f1
Apply suggestion from @Copilot
bpkroth Oct 22, 2025
30b7d6a
Apply suggestion from @Copilot
bpkroth Oct 22, 2025
a4be9fb
remove unused imports
bpkroth Oct 22, 2025
13efd80
Merge branch 'main' into split-docker-tests
bpkroth Oct 23, 2025
1ec818c
Merge branch 'main' into split-docker-tests
bpkroth Oct 23, 2025
ecb7c96
Merge branch 'main' into split-docker-tests
bpkroth Oct 23, 2025
dc9d51a
Merge branch 'main' into split-docker-tests
motus Oct 23, 2025
633bf41
Merge branch 'main' into split-docker-tests
motus Oct 23, 2025
6ef46e9
Merge branch 'main' into split-docker-tests
motus Oct 23, 2025
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
144 changes: 0 additions & 144 deletions mlos_bench/mlos_bench/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@
#
"""Common fixtures for mock TunableGroups and Environment objects."""

import os
import sys
from collections.abc import Generator
from typing import Any

import pytest
from fasteners import InterProcessLock, InterProcessReaderWriterLock
from pytest_docker.plugin import Services as DockerServices
from pytest_docker.plugin import get_docker_services

from mlos_bench.environments.mock_env import MockEnv
from mlos_bench.tests import SEED, resolve_host_name, tunable_groups_fixtures
Expand Down Expand Up @@ -73,141 +67,3 @@ def mock_env_no_noise(tunable_groups: TunableGroups) -> MockEnv:
},
tunables=tunable_groups,
)


# Fixtures to configure the pytest-docker plugin.
@pytest.fixture(scope="session")
def docker_setup() -> list[str] | str:
"""Setup for docker services."""
if sys.platform == "darwin" or os.environ.get("HOST_OSTYPE", "").lower().startswith("darwin"):
# Workaround an oddity on macOS where the "docker-compose up"
# command always recreates the containers.
# That leads to races when multiple workers are trying to
# start and use the same services.
return ["up --build -d --no-recreate"]
else:
return ["up --build -d"]


@pytest.fixture(scope="session")
def docker_compose_file(pytestconfig: pytest.Config) -> list[str]:
"""
Returns the path to the docker-compose file.

Parameters
----------
pytestconfig : pytest.Config

Returns
-------
str
Path to the docker-compose file.
"""
_ = pytestconfig # unused
# TODO: move this closer to the necessary submodules so that different
# docker tests can run independently.
return [
os.path.join(os.path.dirname(__file__), "services", "remote", "ssh", "docker-compose.yml"),
os.path.join(os.path.dirname(__file__), "storage", "sql", "docker-compose.yml"),
# Add additional configs as necessary here.
]


@pytest.fixture(scope="session")
def docker_compose_project_name(short_testrun_uid: str) -> str:
"""
Returns the name of the docker-compose project.

Returns
-------
str
Name of the docker-compose project.
"""
# Use the xdist testrun UID to ensure that the docker-compose project name
# is unique across sessions, but shared amongst workers.
return f"mlos_bench-test-{short_testrun_uid}"


@pytest.fixture(scope="session")
def docker_services_lock(
shared_temp_dir: str,
short_testrun_uid: str,
) -> InterProcessReaderWriterLock:
"""
Gets a pytest session lock for xdist workers to mark when they're using the docker
services.

Yields
------
A lock to ensure that setup/teardown operations don't happen while a
worker is using the docker services.
"""
return InterProcessReaderWriterLock(
f"{shared_temp_dir}/pytest_docker_services-{short_testrun_uid}.lock"
)


@pytest.fixture(scope="session")
def docker_setup_teardown_lock(shared_temp_dir: str, short_testrun_uid: str) -> InterProcessLock:
"""
Gets a pytest session lock between xdist workers for the docker setup/teardown
operations.

Yields
------
A lock to ensure that only one worker is doing setup/teardown at a time.
"""
return InterProcessLock(
f"{shared_temp_dir}/pytest_docker_services-setup-teardown-{short_testrun_uid}.lock"
)


@pytest.fixture(scope="session")
def locked_docker_services(
docker_compose_command: Any,
docker_compose_file: Any,
docker_compose_project_name: Any,
docker_setup: Any,
docker_cleanup: Any,
docker_setup_teardown_lock: InterProcessLock,
docker_services_lock: InterProcessReaderWriterLock,
) -> Generator[DockerServices, Any, None]:
"""A locked version of the docker_services fixture to implement xdist single
instance locking.
"""
# pylint: disable=too-many-arguments,too-many-positional-arguments
# Mark the services as in use with the reader lock.
docker_services_lock.acquire_read_lock()
# Acquire the setup lock to prevent multiple setup operations at once.
docker_setup_teardown_lock.acquire()
# This "with get_docker_services(...)"" pattern is in the default fixture.
# We call it instead of docker_services() to avoid pytest complaints about
# calling fixtures directly.
with get_docker_services(
docker_compose_command,
docker_compose_file,
docker_compose_project_name,
docker_setup,
docker_cleanup,
) as docker_services:
# Release the setup/tear down lock in order to let the setup operation
# continue for other workers (should be a no-op at this point).
docker_setup_teardown_lock.release()
# Yield the services so that tests within this worker can use them.
yield docker_services
# Now tests that use those services get to run on this worker...
# Once the tests are done, release the read lock that marks the services as in use.
docker_services_lock.release_read_lock()
# Now as we prepare to execute the cleanup code on context exit we need
# to acquire the setup/teardown lock again.
# First we attempt to get the write lock so that we wait for other
# readers to finish and guard against a lock inversion possibility.
docker_services_lock.acquire_write_lock()
# Next, acquire the setup/teardown lock
# First one here is the one to do actual work, everyone else is basically a no-op.
# Upon context exit, we should execute the docker_cleanup code.
# And try to get the setup/tear down lock again.
docker_setup_teardown_lock.acquire()
# Finally, after the docker_cleanup code has finished, remove both locks.
docker_setup_teardown_lock.release()
docker_services_lock.release_write_lock()
173 changes: 173 additions & 0 deletions mlos_bench/mlos_bench/tests/docker_fixtures_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
"""
Helper functions for various docker test fixtures.

Test functions needing to use these should import them and then add them to their
namespace in a conftest.py file.

The intent of keeping these separate from conftest.py is to allow individual test to
setup their own docker-compose configurations that are independent.

As such, each conftest.py should set their own docker_compose_file fixture pointing to
the appropriate docker-compose.yml file(s) and set a unique docker_compose_project_name.
"""
# pylint: disable=redefined-outer-name

import os
import sys
from collections.abc import Generator
from typing import Any

import pytest
from fasteners import InterProcessLock, InterProcessReaderWriterLock
from pytest_docker.plugin import Services as DockerServices
from pytest_docker.plugin import get_docker_services


# Fixtures to configure the pytest-docker plugin.
@pytest.fixture(scope="session")
def docker_setup() -> list[str] | str:
"""Setup for docker services."""
if sys.platform == "darwin" or os.environ.get("HOST_OSTYPE", "").lower().startswith("darwin"):
# Workaround an oddity on macOS where the "docker-compose up"
# command always recreates the containers.
# That leads to races when multiple workers are trying to
# start and use the same services.
return ["up --build -d --no-recreate"]
else:
return ["up --build -d"]


@pytest.fixture(scope="session")
def docker_compose_file(pytestconfig: pytest.Config) -> list[str]:
"""
Fixture for the path to the docker-compose file.

Parameters
----------
pytestconfig : pytest.Config

Returns
-------
list[str]
List of paths to the docker-compose file(s).
"""
_ = pytestconfig # unused
# Add additional configs as necessary here.
# return []
raise NotImplementedError("Please implement docker_compose_file in your conftest.py")


@pytest.fixture(scope="session")
def docker_compose_project_name(short_testrun_uid: str) -> str:
"""
Fixture for the name of the docker-compose project.

Returns
-------
str
Name of the docker-compose project.
"""
# Use the xdist testrun UID to ensure that the docker-compose project name
# is unique across sessions, but shared amongst workers.
# return f"""mlos_bench-test-{short_testrun_uid}-{__name__.replace(".", "-")}"""
raise NotImplementedError("Please implement docker_compose_project_name in your conftest.py")


@pytest.fixture(scope="session")
def docker_services_lock(
shared_temp_dir: str,
short_testrun_uid: str,
) -> InterProcessReaderWriterLock:
"""
Gets a pytest session lock for xdist workers to mark when they're using the docker
services.

Yields
------
A lock to ensure that setup/teardown operations don't happen while a
worker is using the docker services.
"""
return InterProcessReaderWriterLock(
f"{shared_temp_dir}/pytest_docker_services-{short_testrun_uid}.lock"
)


@pytest.fixture(scope="session")
def docker_setup_teardown_lock(shared_temp_dir: str, short_testrun_uid: str) -> InterProcessLock:
"""
Gets a pytest session lock between xdist workers for the docker setup/teardown
operations.

Yields
------
A lock to ensure that only one worker is doing setup/teardown at a time.
"""
return InterProcessLock(
f"{shared_temp_dir}/pytest_docker_services-setup-teardown-{short_testrun_uid}.lock"
)


@pytest.fixture(scope="session")
def locked_docker_services(
docker_compose_command: Any,
docker_compose_file: Any,
docker_compose_project_name: Any,
docker_setup: Any,
docker_cleanup: Any,
docker_setup_teardown_lock: InterProcessLock,
docker_services_lock: InterProcessReaderWriterLock,
) -> Generator[DockerServices, Any, None]:
"""A locked version of the docker_services fixture to implement xdist single
instance locking.
"""
# pylint: disable=too-many-arguments,too-many-positional-arguments
# Mark the services as in use with the reader lock.
docker_services_lock.acquire_read_lock()
# Acquire the setup lock to prevent multiple setup operations at once.
docker_setup_teardown_lock.acquire()
# This "with get_docker_services(...)"" pattern is in the default fixture.
# We call it instead of docker_services() to avoid pytest complaints about
# calling fixtures directly.
with get_docker_services(
docker_compose_command,
docker_compose_file,
docker_compose_project_name,
docker_setup,
docker_cleanup,
) as docker_services:
# Release the setup/tear down lock in order to let the setup operation
# continue for other workers (should be a no-op at this point).
docker_setup_teardown_lock.release()
# Yield the services so that tests within this worker can use them.
yield docker_services
# Now tests that use those services get to run on this worker...
# Once the tests are done, release the read lock that marks the services as in use.
docker_services_lock.release_read_lock()
# Now as we prepare to execute the cleanup code on context exit we need
# to acquire the setup/teardown lock again.
# First we attempt to get the write lock so that we wait for other
# readers to finish and guard against a lock inversion possibility.
docker_services_lock.acquire_write_lock()
# Next, acquire the setup/teardown lock
# First one here is the one to do actual work, everyone else is basically a no-op.
# Upon context exit, we should execute the docker_cleanup code.
# And try to get the setup/tear down lock again.
docker_setup_teardown_lock.acquire()
# Finally, after the docker_cleanup code has finished, remove both locks.
docker_setup_teardown_lock.release()
docker_services_lock.release_write_lock()


__all__ = [
# These two should be implemented in the conftest.py of the local test suite
# "docker_compose_file",
# "docker_compose_project_name",
"docker_setup",
"docker_services_lock",
"docker_setup_teardown_lock",
"locked_docker_services",
]
9 changes: 9 additions & 0 deletions mlos_bench/mlos_bench/tests/environments/remote/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
#
"""Fixtures for the RemoteEnv tests using SSH Services."""

import mlos_bench.tests.docker_fixtures_util as docker_fixtures_util
import mlos_bench.tests.services.remote.ssh.fixtures as ssh_fixtures

# Expose some of those as local names so they can be picked up as fixtures by pytest.
docker_compose_file = ssh_fixtures.docker_compose_file
docker_compose_project_name = ssh_fixtures.docker_compose_project_name

docker_setup = docker_fixtures_util.docker_setup
docker_services_lock = docker_fixtures_util.docker_services_lock
docker_setup_teardown_lock = docker_fixtures_util.docker_setup_teardown_lock
locked_docker_services = docker_fixtures_util.locked_docker_services

ssh_test_server = ssh_fixtures.ssh_test_server
9 changes: 9 additions & 0 deletions mlos_bench/mlos_bench/tests/services/remote/ssh/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
#
"""Fixtures for the SSH service tests."""

import mlos_bench.tests.docker_fixtures_util as docker_fixtures_util
import mlos_bench.tests.services.remote.ssh.fixtures as ssh_fixtures

# Expose some of those as local names so they can be picked up as fixtures by pytest.
docker_compose_file = ssh_fixtures.docker_compose_file
docker_compose_project_name = ssh_fixtures.docker_compose_project_name

docker_setup = docker_fixtures_util.docker_setup
docker_services_lock = docker_fixtures_util.docker_services_lock
docker_setup_teardown_lock = docker_fixtures_util.docker_setup_teardown_lock
locked_docker_services = docker_fixtures_util.locked_docker_services

ssh_test_server = ssh_fixtures.ssh_test_server
alt_test_server = ssh_fixtures.alt_test_server
reboot_test_server = ssh_fixtures.reboot_test_server
Expand Down
Loading
Loading