diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4a778ed13a..d619fec293 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -FROM mcr.microsoft.com/vscode/devcontainers/base AS base +# TODO: FIXME: Temporarily pin to bookworm until latest (trixie) base image is fully supported. +# See Also: +# - +# - .devcontainer/devcontainer.json +FROM mcr.microsoft.com/vscode/devcontainers/base:bookworm AS base # Add some additional packages for the devcontainer terminal environment. USER root @@ -80,6 +84,8 @@ RUN echo "Setup miniconda" \ && /opt/conda/bin/conda init \ && /opt/conda/bin/conda config --set channel_priority strict \ && /opt/conda/bin/conda info \ + && /opt/conda/bin/conda config --show \ + && /opt/conda/bin/conda tos accept --override-channels --channel defaults \ && /opt/conda/bin/conda update -v -y -n base -c conda-forge -c defaults --all \ && /opt/conda/bin/conda list -n base \ && /opt/conda/bin/conda install -v -y -n base -c conda-forge -c defaults conda-libmamba-solver \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f9cbbc4a38..5aa3d91102 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,11 +31,22 @@ "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, //"ghcr.io/devcontainers/features/conda:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + // Note: this may be required when moving to Debian Trixie base image. + // See Also: + // - + // - .devcontainer/Dockerfile + //"moby": false + }, "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/stuartleeks/dev-container-features/azure-cli-persistence:0": {}, "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {} }, + "forwardPorts": [ + // Make the nginx instance started as a part of `make doc` available to view + // the coverage results. + 8080 + ], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "python --version", // Configure tool-specific properties. diff --git a/.devcontainer/scripts/run-devcontainer.sh b/.devcontainer/scripts/run-devcontainer.sh index dfd7d3f65d..5cefe57b69 100755 --- a/.devcontainer/scripts/run-devcontainer.sh +++ b/.devcontainer/scripts/run-devcontainer.sh @@ -50,5 +50,6 @@ docker run -it --rm \ --env http_proxy="${http_proxy:-}" \ --env https_proxy="${https_proxy:-}" \ --env no_proxy="${no_proxy:-}" \ + --add-host host.docker.internal:host-gateway \ mlos-devcontainer \ $* diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 44ac8ee424..ae87229f47 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -170,11 +170,18 @@ jobs: test_count=$(docker exec --user vscode --env USER=vscode mlos-devcontainer \ conda run -n mlos python -m pytest -svxl -n auto --collect-only --rootdir /workspaces/MLOS -s --cache-clear \ | grep -c '&2; exit 1; fi + # FIXME: UNDO: if [ "${test_count:-0}" -lt 725 ]; then echo "Expected at least 725 tests, got '$test_count'" >&2; exit 1; fi # Now actually run the tests. docker exec --user vscode --env USER=vscode mlos-devcontainer make CONDA_INFO_LEVEL=-v test + - name: Upload coverage report as build artifact + if: always() # ensures it runs even if tests fail + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.xml + - name: Generate and test binary distribution files timeout-minutes: 10 run: | diff --git a/.vscode/settings.json b/.vscode/settings.json index a01d1d6418..441bee0e5a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -176,5 +176,17 @@ "githubPullRequests.experimental.chat": true, "github.copilot.chat.codesearch.enabled": true, "github.copilot.chat.copilotDebugCommand.enabled": true, - "github.copilot.chat.reviewSelection.enabled": true + "github.copilot.chat.reviewSelection.enabled": true, + "python-envs.defaultEnvManager": "ms-python.python:conda", + "python-envs.defaultPackageManager": "ms-python.python:conda", + // Make the nginx instance started as a part of `make doc` available to view + // the coverage results. + "remote.localPortHost": "localhost", + "remote.SSH.defaultForwardedPorts": [ + { + "name": "nginx for doc viewing", + "localPort": 8080, + "remotePort": 8080 + } + ] } diff --git a/conda-envs/mlos-3.10.yml b/conda-envs/mlos-3.10.yml index 75f7a40022..2fe9aef78c 100644 --- a/conda-envs/mlos-3.10.yml +++ b/conda-envs/mlos-3.10.yml @@ -20,6 +20,9 @@ dependencies: - python=3.10 # See comments in mlos.yml. #- gcc_linux-64 + # Install an SMAC requirement pre-compiled from conda-forge. + # See Also: https://github.com/microsoft/MLOS/issues/1001 + - pyrfr>=0.9.0 - pip: - bump2version - check-jsonschema diff --git a/conda-envs/mlos-3.11.yml b/conda-envs/mlos-3.11.yml index 4a52bfae0a..1cdecc5119 100644 --- a/conda-envs/mlos-3.11.yml +++ b/conda-envs/mlos-3.11.yml @@ -20,6 +20,9 @@ dependencies: - python=3.11 # See comments in mlos.yml. #- gcc_linux-64 + # Install an SMAC requirement pre-compiled from conda-forge. + # See Also: https://github.com/microsoft/MLOS/issues/1001 + - pyrfr>=0.9.0 - pip: - bump2version - check-jsonschema diff --git a/conda-envs/mlos-3.12.yml b/conda-envs/mlos-3.12.yml index 2a1a33f079..ac01f11712 100644 --- a/conda-envs/mlos-3.12.yml +++ b/conda-envs/mlos-3.12.yml @@ -22,6 +22,9 @@ dependencies: - python=3.12 # See comments in mlos.yml. #- gcc_linux-64 + # Install an SMAC requirement pre-compiled from conda-forge. + # See Also: https://github.com/microsoft/MLOS/issues/1001 + - pyrfr>=0.9.0 - pip: - bump2version - check-jsonschema diff --git a/conda-envs/mlos-3.13.yml b/conda-envs/mlos-3.13.yml index 5887328dd7..9e668db065 100644 --- a/conda-envs/mlos-3.13.yml +++ b/conda-envs/mlos-3.13.yml @@ -22,6 +22,9 @@ dependencies: - python=3.13 # See comments in mlos.yml. #- gcc_linux-64 + # Install an SMAC requirement pre-compiled from conda-forge. + # See Also: https://github.com/microsoft/MLOS/issues/1001 + - pyrfr>=0.9.0 - pip: - bump2version - check-jsonschema diff --git a/conda-envs/mlos.yml b/conda-envs/mlos.yml index 25045bcdfc..ee05dfe1d3 100644 --- a/conda-envs/mlos.yml +++ b/conda-envs/mlos.yml @@ -18,6 +18,9 @@ dependencies: # FIXME: Temporarily avoid broken libpq that's missing client headers. - libpq<17.0 - python + # Install an SMAC requirement pre-compiled from conda-forge. + # See Also: https://github.com/microsoft/MLOS/issues/1001 + - pyrfr>=0.9.0 - pip: - bump2version - check-jsonschema @@ -28,7 +31,7 @@ dependencies: - pylint==3.3.7 - tomlkit - mypy==1.15.0 - - pyright==1.1.400 + - pyright==1.1.405 - pandas-stubs - types-beautifulsoup4 - types-colorama diff --git a/mlos_bench/mlos_bench/optimizers/mlos_core_optimizer.py b/mlos_bench/mlos_bench/optimizers/mlos_core_optimizer.py index 0ef9751228..ba3cfb33c8 100644 --- a/mlos_bench/mlos_bench/optimizers/mlos_core_optimizer.py +++ b/mlos_bench/mlos_bench/optimizers/mlos_core_optimizer.py @@ -285,7 +285,7 @@ def bulk_register( if status is not None: # Select only the completed trials, set scores for failed trials to +inf. - df_status = pd.Series(status) + df_status = pd.Series(list(status), dtype=object) # TODO: Be more flexible with values used for failed trials (not just +inf). # Issue: https://github.com/microsoft/MLOS/issues/523 df_scores[df_status != Status.SUCCEEDED] = float("inf") diff --git a/mlos_bench/mlos_bench/storage/sql/alembic/versions/b61aa446e724_support_fractional_seconds_with_mysql.py b/mlos_bench/mlos_bench/storage/sql/alembic/versions/b61aa446e724_support_fractional_seconds_with_mysql.py index 7e41b93f09..d65a6344c6 100644 --- a/mlos_bench/mlos_bench/storage/sql/alembic/versions/b61aa446e724_support_fractional_seconds_with_mysql.py +++ b/mlos_bench/mlos_bench/storage/sql/alembic/versions/b61aa446e724_support_fractional_seconds_with_mysql.py @@ -34,8 +34,8 @@ def _mysql_datetime(*, with_fsp: bool = False) -> mysql.DATETIME: See for details. """ if with_fsp: - return mysql.DATETIME(fsp=6) # type: ignore[no-untyped-call] - return mysql.DATETIME() # type: ignore[no-untyped-call] + return mysql.DATETIME(fsp=6) + return mysql.DATETIME() def upgrade() -> None: diff --git a/mlos_bench/mlos_bench/storage/sql/schema.py b/mlos_bench/mlos_bench/storage/sql/schema.py index 475a772312..23f5676f93 100644 --- a/mlos_bench/mlos_bench/storage/sql/schema.py +++ b/mlos_bench/mlos_bench/storage/sql/schema.py @@ -56,7 +56,7 @@ def _mysql_datetime_with_fsp() -> mysql.DATETIME: Split out to allow single mypy ignore. See for details. """ - return mysql.DATETIME(fsp=6) # type: ignore[no-untyped-call] + return mysql.DATETIME(fsp=6) class _DDL: diff --git a/mlos_bench/mlos_bench/tests/__init__.py b/mlos_bench/mlos_bench/tests/__init__.py index 778d89836d..322c5005b4 100644 --- a/mlos_bench/mlos_bench/tests/__init__.py +++ b/mlos_bench/mlos_bench/tests/__init__.py @@ -13,7 +13,7 @@ import shutil import socket from datetime import tzinfo -from logging import debug, warning +from logging import warning from subprocess import run import pytest @@ -52,8 +52,17 @@ if cmd.returncode != 0 or not any( line for line in stdout.splitlines() if "Platform" in line and "linux" in line ): - debug("Docker is available but missing support for targeting linux platform.") DOCKER = None + warning("Docker is available but missing buildx support for targeting linux platform.") + raise RuntimeError( + "DEBUGGING: " + "Docker is available but missing buildx support for targeting linux platform." + ) +if not DOCKER: + warning("Docker is not available on this system. Some tests will be skipped.") + raise RuntimeError( + "DEBUGGING: Docker is not available on this system. Some tests will be skipped." + ) requires_docker = pytest.mark.skipif( not DOCKER, reason="Docker with Linux support is not available on this system.", @@ -62,6 +71,11 @@ # A decorator for tests that require ssh. # Use with @requires_ssh above a test_...() function. SSH = shutil.which("ssh") +if not SSH: + warning("ssh is not available on this system. Some tests will be skipped.") + raise RuntimeError( + "DEBUGGING: ssh is not available on this system. Some tests will be skipped." + ) requires_ssh = pytest.mark.skipif(not SSH, reason="ssh is not available on this system.") # A common seed to use to avoid tracking down race conditions and intermingling @@ -112,7 +126,7 @@ def wait_docker_service_healthy( docker_services: DockerServices, project_name: str, service_name: str, - timeout: float = 30.0, + timeout: float = 60.0, ) -> None: """Wait until a docker service is healthy.""" docker_services.wait_until_responsive( @@ -126,7 +140,7 @@ def wait_docker_service_socket(docker_services: DockerServices, hostname: str, p """Wait until a docker service is ready.""" docker_services.wait_until_responsive( check=lambda: check_socket(hostname, port), - timeout=30.0, + timeout=60.0, pause=0.5, ) diff --git a/mlos_bench/mlos_bench/tests/conftest.py b/mlos_bench/mlos_bench/tests/conftest.py index 59dfbff9d2..59ed4b97bd 100644 --- a/mlos_bench/mlos_bench/tests/conftest.py +++ b/mlos_bench/mlos_bench/tests/conftest.py @@ -104,6 +104,8 @@ def docker_compose_file(pytestconfig: pytest.Config) -> list[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"), diff --git a/mlos_viz/mlos_viz/base.py b/mlos_viz/mlos_viz/base.py index 2350e99470..626e6c6979 100644 --- a/mlos_viz/mlos_viz/base.py +++ b/mlos_viz/mlos_viz/base.py @@ -30,7 +30,7 @@ def _get_kwarg_defaults(target: Callable, **kwargs: Any) -> dict[str, Any]: Note: this only works with non-positional kwargs (e.g., those after a * arg). """ target_kwargs = {} - for kword in target.__kwdefaults__: # or {} # intentionally omitted for now + for kword in target.__kwdefaults__ or {}: if kword in kwargs: target_kwargs[kword] = kwargs[kword] return target_kwargs @@ -59,6 +59,13 @@ def ignore_plotter_warnings() -> None: ), ) + warnings.filterwarnings( + "ignore", + module="matplotlib", + category=DeprecationWarning, + message="'mode' parameter is deprecated and will be removed in Pillow 13", + ) + def _add_groupby_desc_column( results_df: pandas.DataFrame, diff --git a/mlos_viz/mlos_viz/dabl.py b/mlos_viz/mlos_viz/dabl.py index e0b033b7a8..7ad636e8c5 100644 --- a/mlos_viz/mlos_viz/dabl.py +++ b/mlos_viz/mlos_viz/dabl.py @@ -101,3 +101,10 @@ def ignore_plotter_warnings() -> None: module="dabl", message="The legendHandles attribute was deprecated in Matplotlib 3.7 and will be removed", ) + + warnings.filterwarnings( + "ignore", + module="matplotlib", + category=DeprecationWarning, + message="'mode' parameter is deprecated and will be removed in Pillow 13", + )