diff --git a/.github/workflows/test-integrations-tasks.yml b/.github/workflows/test-integrations-tasks.yml index 0038f1d050..d84789b767 100644 --- a/.github/workflows/test-integrations-tasks.yml +++ b/.github/workflows/test-integrations-tasks.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7","3.10","3.11","3.12","3.13"] + python-version: ["3.7","3.12","3.13"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index ce9fdefe48..346cabc2db 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -253,6 +253,11 @@ "py3.8": ["taskgroup==0.0.0a4"], }, }, + "ray": { + "package": "ray", + "python": ">=3.9", + "num_versions": 2, + }, "redis_py_cluster_legacy": { "package": "redis-py-cluster", }, diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index a545eed330..a3f1ec8fea 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -70,7 +70,6 @@ # of these from the IGNORE list "gcp", "httpx", - "ray", "redis", "requests", "rq", diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index 0522c60231..9c511d3f1e 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -61,10 +61,6 @@ envlist = # OpenTelemetry Experimental (POTel) {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel - # Ray - {py3.10,py3.11}-ray-v{2.34} - {py3.10,py3.11}-ray-latest - # Redis {py3.6,py3.8}-redis-v{3} {py3.7,py3.8,py3.11}-redis-v{4} @@ -168,10 +164,6 @@ deps = # OpenTelemetry Experimental (POTel) potel: -e .[opentelemetry-experimental] - # Ray - ray-v2.34: ray~=2.34.0 - ray-latest: ray - # Redis redis: fakeredis!=1.7.4 redis: pytest<8.0.0 diff --git a/tests/integrations/ray/test_ray.py b/tests/integrations/ray/test_ray.py index b5bdd473c4..f4e67df038 100644 --- a/tests/integrations/ray/test_ray.py +++ b/tests/integrations/ray/test_ray.py @@ -1,6 +1,8 @@ import json import os import pytest +import shutil +import uuid import ray @@ -10,6 +12,12 @@ from tests.conftest import TestTransport +@pytest.fixture(autouse=True) +def shutdown_ray(tmpdir): + yield + ray.shutdown() + + class RayTestTransport(TestTransport): def __init__(self): self.envelopes = [] @@ -20,9 +28,6 @@ def capture_envelope(self, envelope: Envelope) -> None: class RayLoggingTransport(TestTransport): - def __init__(self): - super().__init__() - def capture_envelope(self, envelope: Envelope) -> None: print(envelope.serialize().decode("utf-8", "replace")) @@ -39,13 +44,24 @@ def setup_sentry(transport=None): ) -def read_error_from_log(job_id): - log_dir = "/tmp/ray/session_latest/logs/" +def read_error_from_log(job_id, ray_temp_dir): + # Find the actual session directory that Ray created + session_dirs = [d for d in os.listdir(ray_temp_dir) if d.startswith("session_")] + if not session_dirs: + raise FileNotFoundError(f"No session directory found in {ray_temp_dir}") + + session_dir = os.path.join(ray_temp_dir, session_dirs[0]) + log_dir = os.path.join(session_dir, "logs") + + if not os.path.exists(log_dir): + raise FileNotFoundError(f"No logs directory found at {log_dir}") + log_file = [ f for f in os.listdir(log_dir) if "worker" in f and job_id in f and f.endswith(".out") ][0] + with open(os.path.join(log_dir, log_file), "r") as file: lines = file.readlines() @@ -58,7 +74,6 @@ def read_error_from_log(job_id): return error -@pytest.mark.forked @pytest.mark.parametrize( "task_options", [{}, {"num_cpus": 0, "memory": 1024 * 1024 * 10}] ) @@ -124,40 +139,47 @@ def example_task(): ) -@pytest.mark.forked def test_errors_in_ray_tasks(): setup_sentry_with_logging_transport() - ray.init( - runtime_env={ - "worker_process_setup_hook": setup_sentry_with_logging_transport, - "working_dir": "./", - } - ) + ray_temp_dir = os.path.join("/tmp", f"ray_test_{uuid.uuid4().hex[:8]}") + os.makedirs(ray_temp_dir, exist_ok=True) - # Setup ray task - @ray.remote - def example_task(): - 1 / 0 + try: + ray.init( + runtime_env={ + "worker_process_setup_hook": setup_sentry_with_logging_transport, + "working_dir": "./", + }, + _temp_dir=ray_temp_dir, + ) - with sentry_sdk.start_transaction(op="task", name="ray test transaction"): - with pytest.raises(ZeroDivisionError): - future = example_task.remote() - ray.get(future) + # Setup ray task + @ray.remote + def example_task(): + 1 / 0 - job_id = future.job_id().hex() - error = read_error_from_log(job_id) + with sentry_sdk.start_transaction(op="task", name="ray test transaction"): + with pytest.raises(ZeroDivisionError): + future = example_task.remote() + ray.get(future) - assert error["level"] == "error" - assert ( - error["transaction"] - == "tests.integrations.ray.test_ray.test_errors_in_ray_tasks..example_task" - ) - assert error["exception"]["values"][0]["mechanism"]["type"] == "ray" - assert not error["exception"]["values"][0]["mechanism"]["handled"] + job_id = future.job_id().hex() + error = read_error_from_log(job_id, ray_temp_dir) + + assert error["level"] == "error" + assert ( + error["transaction"] + == "tests.integrations.ray.test_ray.test_errors_in_ray_tasks..example_task" + ) + assert error["exception"]["values"][0]["mechanism"]["type"] == "ray" + assert not error["exception"]["values"][0]["mechanism"]["handled"] + + finally: + if os.path.exists(ray_temp_dir): + shutil.rmtree(ray_temp_dir, ignore_errors=True) -@pytest.mark.forked def test_tracing_in_ray_actors(): setup_sentry() @@ -194,37 +216,45 @@ def increment(self): assert worker_envelopes == [] -@pytest.mark.forked def test_errors_in_ray_actors(): setup_sentry_with_logging_transport() - ray.init( - runtime_env={ - "worker_process_setup_hook": setup_sentry_with_logging_transport, - "working_dir": "./", - } - ) - - # Setup ray actor - @ray.remote - class Counter: - def __init__(self): - self.n = 0 - - def increment(self): - with sentry_sdk.start_span(op="task", name="example actor execution"): - 1 / 0 - - return sentry_sdk.get_client().transport.envelopes - - with sentry_sdk.start_transaction(op="task", name="ray test transaction"): - with pytest.raises(ZeroDivisionError): - counter = Counter.remote() - future = counter.increment.remote() - ray.get(future) - - job_id = future.job_id().hex() - error = read_error_from_log(job_id) - - # We do not capture errors in ray actors yet - assert error is None + ray_temp_dir = os.path.join("/tmp", f"ray_test_{uuid.uuid4().hex[:8]}") + os.makedirs(ray_temp_dir, exist_ok=True) + + try: + ray.init( + runtime_env={ + "worker_process_setup_hook": setup_sentry_with_logging_transport, + "working_dir": "./", + }, + _temp_dir=ray_temp_dir, + ) + + # Setup ray actor + @ray.remote + class Counter: + def __init__(self): + self.n = 0 + + def increment(self): + with sentry_sdk.start_span(op="task", name="example actor execution"): + 1 / 0 + + return sentry_sdk.get_client().transport.envelopes + + with sentry_sdk.start_transaction(op="task", name="ray test transaction"): + with pytest.raises(ZeroDivisionError): + counter = Counter.remote() + future = counter.increment.remote() + ray.get(future) + + job_id = future.job_id().hex() + error = read_error_from_log(job_id, ray_temp_dir) + + # We do not capture errors in ray actors yet + assert error is None + + finally: + if os.path.exists(ray_temp_dir): + shutil.rmtree(ray_temp_dir, ignore_errors=True) diff --git a/tox.ini b/tox.ini index e2898ad8f3..15192bc0ad 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-09-18T08:05:49.500134+00:00 +# Last generated: 2025-09-18T10:26:22.484602+00:00 [tox] requires = @@ -61,10 +61,6 @@ envlist = # OpenTelemetry Experimental (POTel) {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel - # Ray - {py3.10,py3.11}-ray-v{2.34} - {py3.10,py3.11}-ray-latest - # Redis {py3.6,py3.8}-redis-v{3} {py3.7,py3.8,py3.11}-redis-v{4} @@ -233,6 +229,9 @@ envlist = {py3.6,py3.7}-huey-v2.3.2 {py3.6,py3.11,py3.12}-huey-v2.5.3 + {py3.9,py3.10}-ray-v2.7.2 + {py3.9,py3.12,py3.13}-ray-v2.49.1 + {py3.8,py3.9}-spark-v3.0.3 {py3.8,py3.10,py3.11}-spark-v3.5.6 {py3.9,py3.12,py3.13}-spark-v4.0.1 @@ -396,10 +395,6 @@ deps = # OpenTelemetry Experimental (POTel) potel: -e .[opentelemetry-experimental] - # Ray - ray-v2.34: ray~=2.34.0 - ray-latest: ray - # Redis redis: fakeredis!=1.7.4 redis: pytest<8.0.0 @@ -631,6 +626,9 @@ deps = huey-v2.3.2: huey==2.3.2 huey-v2.5.3: huey==2.5.3 + ray-v2.7.2: ray==2.7.2 + ray-v2.49.1: ray==2.49.1 + spark-v3.0.3: pyspark==3.0.3 spark-v3.5.6: pyspark==3.5.6 spark-v4.0.1: pyspark==4.0.1