Files
ray-project--ray/python/ray/tune/tests/test_progress_reporter.py
T
2026-07-13 13:17:40 +08:00

863 lines
35 KiB
Python

import collections
import os
import unittest
from unittest.mock import MagicMock, Mock, patch
import numpy as np
import pytest
import regex as re
from ray import tune
from ray._common.test_utils import run_string_as_driver
from ray.tune.experiment.trial import Trial
from ray.tune.progress_reporter import (
CLIReporter,
JupyterNotebookReporter,
ProgressReporter,
TuneReporterBase,
_best_trial_str,
_detect_reporter,
_fair_filter_trials,
_max_len,
_time_passed_str,
_trial_progress_str,
)
from ray.tune.result import AUTO_RESULT_KEYS
EXPECTED_RESULT_1 = """Result logdir: /foo
Number of trials: 5 (1 PENDING, 3 RUNNING, 1 TERMINATED)
+--------------+------------+-------+-----+-----+------------+
| Trial name | status | loc | a | b | metric_1 |
|--------------+------------+-------+-----+-----+------------|
| 00002 | RUNNING | here | 2 | 4 | 1 |
| 00001 | PENDING | here | 1 | 2 | 0.5 |
| 00000 | TERMINATED | here | 0 | 0 | 0 |
+--------------+------------+-------+-----+-----+------------+
... 2 more trials not shown (2 RUNNING)"""
EXPECTED_RESULT_2 = """Result logdir: /foo
Number of trials: 5 (1 PENDING, 3 RUNNING, 1 TERMINATED)
+--------------+------------+-------+-----+-----+---------+---------+
| Trial name | status | loc | a | b | n/k/0 | n/k/1 |
|--------------+------------+-------+-----+-----+---------+---------|
| 00002 | RUNNING | here | 2 | 4 | 2 | 4 |
| 00003 | RUNNING | here | 3 | 6 | 3 | 6 |
| 00004 | RUNNING | here | 4 | 8 | 4 | 8 |
| 00001 | PENDING | here | 1 | 2 | 1 | 2 |
| 00000 | TERMINATED | here | 0 | 0 | 0 | 0 |
+--------------+------------+-------+-----+-----+---------+---------+"""
EXPECTED_RESULT_3 = """Result logdir: /foo
Number of trials: 5 (1 PENDING, 3 RUNNING, 1 TERMINATED)
+--------------+------------+-------+-----+-----------+------------+
| Trial name | status | loc | A | NestSub | Metric 2 |
|--------------+------------+-------+-----+-----------+------------|
| 00002 | RUNNING | here | 2 | 1 | 0.5 |
| 00001 | PENDING | here | 1 | 0.5 | 0.25 |
| 00000 | TERMINATED | here | 0 | 0 | 0 |
+--------------+------------+-------+-----+-----------+------------+
... 2 more trials not shown (2 RUNNING)"""
EXPECTED_RESULT_4 = """Result logdir: /foo
Number of trials: 5 (1 PENDING, 3 RUNNING, 1 TERMINATED)
+--------------+------------+-------+-----+-----+------------+
| Trial name | status | loc | a | b | metric_1 |
|--------------+------------+-------+-----+-----+------------|
| 00002 | RUNNING | here | 2 | 4 | 1 |
| 00003 | RUNNING | here | 3 | 6 | 1.5 |
| 00004 | RUNNING | here | 4 | 8 | 2 |
| 00001 | PENDING | here | 1 | 2 | 0.5 |
| 00000 | TERMINATED | here | 0 | 0 | 0 |
+--------------+------------+-------+-----+-----+------------+"""
END_TO_END_COMMAND = """
import ray
from ray import tune
from ray.tune.experiment.trial import _Location
from ray.tune.progress_reporter import _get_trial_location
from unittest.mock import patch
def mock_get_trial_location(trial, result):
location = _get_trial_location(trial, result)
if location.pid:
return _Location("123.123.123.123", "1")
return location
with patch("ray.tune.progress_reporter._get_trial_location",
mock_get_trial_location):
reporter = tune.progress_reporter.CLIReporter(metric_columns=["done"])
def f(config):
return {"done": True}
ray.init(num_cpus=1)
tune.run_experiments(
{
"one": {
"run": f,
"config": {
"a": tune.grid_search(list(range(10))),
},
},
"two": {
"run": f,
"config": {
"b": tune.grid_search(list(range(10))),
},
},
"three": {
"run": f,
"config": {
"c": tune.grid_search(list(range(10))),
},
},
},
verbose=3,
progress_reporter=reporter)"""
EXPECTED_END_TO_END_START = """Number of trials: 30/30 (29 PENDING, 1 RUNNING)
+---------------+----------+-------------------+-----+-----+
| Trial name | status | loc | a | b |
|---------------+----------+-------------------+-----+-----|
| f_xxxxx_00000 | RUNNING | 123.123.123.123:1 | 0 | |
| f_xxxxx_00001 | PENDING | | 1 | |"""
EXPECTED_END_TO_END_END = """Number of trials: 30/30 (30 TERMINATED)
+---------------+------------+-------------------+-----+-----+-----+--------+
| Trial name | status | loc | a | b | c | done |
|---------------+------------+-------------------+-----+-----+-----+--------|
| f_xxxxx_00000 | TERMINATED | 123.123.123.123:1 | 0 | | | True |
| f_xxxxx_00001 | TERMINATED | 123.123.123.123:1 | 1 | | | True |
| f_xxxxx_00002 | TERMINATED | 123.123.123.123:1 | 2 | | | True |
| f_xxxxx_00003 | TERMINATED | 123.123.123.123:1 | 3 | | | True |
| f_xxxxx_00004 | TERMINATED | 123.123.123.123:1 | 4 | | | True |
| f_xxxxx_00005 | TERMINATED | 123.123.123.123:1 | 5 | | | True |
| f_xxxxx_00006 | TERMINATED | 123.123.123.123:1 | 6 | | | True |
| f_xxxxx_00007 | TERMINATED | 123.123.123.123:1 | 7 | | | True |
| f_xxxxx_00008 | TERMINATED | 123.123.123.123:1 | 8 | | | True |
| f_xxxxx_00009 | TERMINATED | 123.123.123.123:1 | 9 | | | True |
| f_xxxxx_00010 | TERMINATED | 123.123.123.123:1 | | 0 | | True |
| f_xxxxx_00011 | TERMINATED | 123.123.123.123:1 | | 1 | | True |
| f_xxxxx_00012 | TERMINATED | 123.123.123.123:1 | | 2 | | True |
| f_xxxxx_00013 | TERMINATED | 123.123.123.123:1 | | 3 | | True |
| f_xxxxx_00014 | TERMINATED | 123.123.123.123:1 | | 4 | | True |
| f_xxxxx_00015 | TERMINATED | 123.123.123.123:1 | | 5 | | True |
| f_xxxxx_00016 | TERMINATED | 123.123.123.123:1 | | 6 | | True |
| f_xxxxx_00017 | TERMINATED | 123.123.123.123:1 | | 7 | | True |
| f_xxxxx_00018 | TERMINATED | 123.123.123.123:1 | | 8 | | True |
| f_xxxxx_00019 | TERMINATED | 123.123.123.123:1 | | 9 | | True |
| f_xxxxx_00020 | TERMINATED | 123.123.123.123:1 | | | 0 | True |
| f_xxxxx_00021 | TERMINATED | 123.123.123.123:1 | | | 1 | True |
| f_xxxxx_00022 | TERMINATED | 123.123.123.123:1 | | | 2 | True |
| f_xxxxx_00023 | TERMINATED | 123.123.123.123:1 | | | 3 | True |
| f_xxxxx_00024 | TERMINATED | 123.123.123.123:1 | | | 4 | True |
| f_xxxxx_00025 | TERMINATED | 123.123.123.123:1 | | | 5 | True |
| f_xxxxx_00026 | TERMINATED | 123.123.123.123:1 | | | 6 | True |
| f_xxxxx_00027 | TERMINATED | 123.123.123.123:1 | | | 7 | True |
| f_xxxxx_00028 | TERMINATED | 123.123.123.123:1 | | | 8 | True |
| f_xxxxx_00029 | TERMINATED | 123.123.123.123:1 | | | 9 | True |
+---------------+------------+-------------------+-----+-----+-----+--------+""" # noqa
EXPECTED_END_TO_END_AC = """Number of trials: 30/30 (30 TERMINATED)
+---------------+------------+-------+-----+-----+-----+
| Trial name | status | loc | a | b | c |
|---------------+------------+-------+-----+-----+-----|
| f_xxxxx_00000 | TERMINATED | | 0 | | |
| f_xxxxx_00001 | TERMINATED | | 1 | | |
| f_xxxxx_00002 | TERMINATED | | 2 | | |
| f_xxxxx_00003 | TERMINATED | | 3 | | |
| f_xxxxx_00004 | TERMINATED | | 4 | | |
| f_xxxxx_00005 | TERMINATED | | 5 | | |
| f_xxxxx_00006 | TERMINATED | | 6 | | |
| f_xxxxx_00007 | TERMINATED | | 7 | | |
| f_xxxxx_00008 | TERMINATED | | 8 | | |
| f_xxxxx_00009 | TERMINATED | | 9 | | |
| f_xxxxx_00010 | TERMINATED | | | 0 | |
| f_xxxxx_00011 | TERMINATED | | | 1 | |
| f_xxxxx_00012 | TERMINATED | | | 2 | |
| f_xxxxx_00013 | TERMINATED | | | 3 | |
| f_xxxxx_00014 | TERMINATED | | | 4 | |
| f_xxxxx_00015 | TERMINATED | | | 5 | |
| f_xxxxx_00016 | TERMINATED | | | 6 | |
| f_xxxxx_00017 | TERMINATED | | | 7 | |
| f_xxxxx_00018 | TERMINATED | | | 8 | |
| f_xxxxx_00019 | TERMINATED | | | 9 | |
| f_xxxxx_00020 | TERMINATED | | | | 0 |
| f_xxxxx_00021 | TERMINATED | | | | 1 |
| f_xxxxx_00022 | TERMINATED | | | | 2 |
| f_xxxxx_00023 | TERMINATED | | | | 3 |
| f_xxxxx_00024 | TERMINATED | | | | 4 |
| f_xxxxx_00025 | TERMINATED | | | | 5 |
| f_xxxxx_00026 | TERMINATED | | | | 6 |
| f_xxxxx_00027 | TERMINATED | | | | 7 |
| f_xxxxx_00028 | TERMINATED | | | | 8 |
| f_xxxxx_00029 | TERMINATED | | | | 9 |
+---------------+------------+-------+-----+-----+-----+"""
EXPECTED_BEST_1 = (
"Current best trial: 00001 with metric_1=0.5 and "
"parameters={'a': 1, 'b': 2, 'n': {'k': [1, 2]}}"
)
EXPECTED_BEST_2 = "Current best trial: 00004 with metric_1=2.0 and parameters={'a': 4}"
EXPECTED_SORT_RESULT_UNSORTED = """Number of trials: 5 (1 PENDING, 1 RUNNING, 3 TERMINATED)
+--------------+------------+-------+-----+------------+
| Trial name | status | loc | a | metric_1 |
|--------------+------------+-------+-----+------------|
| 00004 | RUNNING | here | 4 | |
| 00003 | PENDING | here | 3 | |
| 00000 | TERMINATED | here | 0 | 0.3 |
| 00001 | TERMINATED | here | 1 | 0.2 |
+--------------+------------+-------+-----+------------+
... 1 more trials not shown (1 TERMINATED)"""
EXPECTED_SORT_RESULT_ASC = """Number of trials: 5 (1 PENDING, 1 RUNNING, 3 TERMINATED)
+--------------+------------+-------+-----+------------+
| Trial name | status | loc | a | metric_1 |
|--------------+------------+-------+-----+------------|
| 00004 | RUNNING | here | 4 | |
| 00003 | PENDING | here | 3 | |
| 00001 | TERMINATED | here | 1 | 0.2 |
| 00000 | TERMINATED | here | 0 | 0.3 |
+--------------+------------+-------+-----+------------+
... 1 more trials not shown (1 TERMINATED)"""
EXPECTED_NESTED_SORT_RESULT = """Number of trials: 5 (1 PENDING, 1 RUNNING, 3 TERMINATED)
+--------------+------------+-------+-----+-------------------+
| Trial name | status | loc | a | nested/metric_2 |
|--------------+------------+-------+-----+-------------------|
| 00004 | RUNNING | here | 4 | |
| 00003 | PENDING | here | 3 | |
| 00001 | TERMINATED | here | 1 | 0.2 |
| 00000 | TERMINATED | here | 0 | 0.3 |
+--------------+------------+-------+-----+-------------------+
... 1 more trials not shown (1 TERMINATED)"""
EXPECTED_SORT_RESULT_DESC = """Number of trials: 5 (1 PENDING, 1 RUNNING, 3 TERMINATED)
+--------------+------------+-------+-----+------------+
| Trial name | status | loc | a | metric_1 |
|--------------+------------+-------+-----+------------|
| 00004 | RUNNING | here | 4 | |
| 00003 | PENDING | here | 3 | |
| 00002 | TERMINATED | here | 2 | 0.4 |
| 00000 | TERMINATED | here | 0 | 0.3 |
+--------------+------------+-------+-----+------------+
... 1 more trials not shown (1 TERMINATED)"""
VERBOSE_EXP_OUT_1 = "Number of trials: 3/3 (2 PENDING, 1 RUNNING)"
VERBOSE_EXP_OUT_2 = "Number of trials: 3/3 (3 TERMINATED)"
VERBOSE_TRIAL_NORM_1 = (
"Trial train_fn_xxxxx_00000 reported acc=5 "
"with parameters={'do': 'complete'}. This trial completed.\n"
)
# NOTE: We use Regex for `VERBOSE_TRIAL_NORM_2` to make the test deterministic.
# `"Trial train_fn_xxxxx_00001 reported..."` and
# `"Trial train_fn_xxxxx_00001 completed..."`
# are printed in separate calls. Sometimes, a status update is printed between the
# calls. For more information, see #29693.
VERBOSE_TRIAL_NORM_2_PATTERN = (
r"Trial train_fn_xxxxx_00001 reported _metric=6 "
r"with parameters=\{'do': 'once'\}\.\n"
r"(?s).*"
r"Trial train_fn_xxxxx_00001 completed\. Last result: _metric=6\n"
)
VERBOSE_TRIAL_NORM_3 = (
"Trial train_fn_xxxxx_00002 reported acc=7 with parameters={'do': 'twice'}.\n"
)
VERBOSE_TRIAL_NORM_4 = (
"Trial train_fn_xxxxx_00002 reported acc=8 "
"with parameters={'do': 'twice'}. This trial completed.\n"
)
VERBOSE_TRIAL_WITH_ONCE_RESULT = "Result for train_fn_xxxxx_00001"
VERBOSE_TRIAL_WITH_ONCE_COMPLETED = "Trial train_fn_xxxxx_00001 completed."
VERBOSE_TRIAL_DETAIL = """+-------------------+----------+-------------------+----------+
| Trial name | status | loc | do |
|-------------------+----------+-------------------+----------|
| train_fn_xxxxx_00000 | RUNNING | 123.123.123.123:1 | complete |"""
VERBOSE_CMD = """import ray.tune
import random
import numpy as np
import time
from ray.tune.experiment.trial import _Location
from ray.tune.progress_reporter import _get_trial_location
from unittest.mock import patch
def mock_get_trial_location(trial, result):
location = _get_trial_location(trial, result)
if location.pid:
return _Location("123.123.123.123", "1")
return location
def train_fn(config):
if config["do"] == "complete":
time.sleep(0.1)
ray.tune.report(dict(acc=5, done=True))
elif config["do"] == "once":
time.sleep(0.5)
return 6
else:
time.sleep(1.0)
ray.tune.report(dict(acc=7))
ray.tune.report(dict(acc=8))
random.seed(1234)
np.random.seed(1234)
with patch("ray.tune.progress_reporter._get_trial_location",
mock_get_trial_location):
ray.tune.run(
train_fn,
config={
"do": ray.tune.grid_search(["complete", "once", "twice"])
},"""
# Add "verbose=3)" etc
class ProgressReporterTest(unittest.TestCase):
def setUp(self) -> None:
os.environ["TUNE_MAX_PENDING_TRIALS_PG"] = "auto"
os.environ["RAY_AIR_NEW_OUTPUT"] = "0"
def mock_trial(self, status, i):
mock = MagicMock()
mock.status = status
mock.trial_id = "%05d" % i
return mock
def testFairFilterTrials(self):
"""Tests that trials are represented fairly."""
trials_by_state = collections.defaultdict(list)
# States for which trials are under and overrepresented
states_under = (Trial.PAUSED, Trial.ERROR)
states_over = (Trial.PENDING, Trial.RUNNING, Trial.TERMINATED)
max_trials = 13
num_trials_under = 2 # num of trials for each underrepresented state
num_trials_over = 10 # num of trials for each overrepresented state
i = 0
for state in states_under:
for _ in range(num_trials_under):
trials_by_state[state].append(self.mock_trial(state, i))
i += 1
for state in states_over:
for _ in range(num_trials_over):
trials_by_state[state].append(self.mock_trial(state, i))
i += 1
filtered_trials_by_state = _fair_filter_trials(
trials_by_state, max_trials=max_trials
)
for state in trials_by_state:
if state in states_under:
expected_num_trials = num_trials_under
else:
expected_num_trials = (
max_trials - num_trials_under * len(states_under)
) / len(states_over)
state_trials = filtered_trials_by_state[state]
self.assertEqual(len(state_trials), expected_num_trials)
# Make sure trials are sorted newest-first within state.
for i in range(len(state_trials) - 1):
assert state_trials[i].trial_id < state_trials[i + 1].trial_id
def testAddMetricColumn(self):
"""Tests edge cases of add_metric_column."""
# Test list-initialized metric columns.
reporter = CLIReporter(metric_columns=["foo", "bar"])
with self.assertRaises(ValueError):
reporter.add_metric_column("bar")
with self.assertRaises(ValueError):
reporter.add_metric_column("baz", "qux")
reporter.add_metric_column("baz")
self.assertIn("baz", reporter._metric_columns)
# Test default-initialized (dict) metric columns.
reporter = CLIReporter()
reporter.add_metric_column("foo", "bar")
self.assertIn("foo", reporter._metric_columns)
def testInfer(self):
reporter = CLIReporter()
test_result = dict(foo_result=1, baz_result=4123, bar_result="testme")
def test(config):
for i in range(3):
tune.report(test_result)
analysis = tune.run(test, num_samples=3, verbose=3)
all_trials = analysis.trials
inferred_results = reporter._infer_user_metrics(all_trials)
for metric in inferred_results:
self.assertNotIn(metric, AUTO_RESULT_KEYS)
self.assertTrue(metric in test_result)
class TestReporter(CLIReporter):
_output = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._max_report_freqency = 0
def report(self, *args, **kwargs):
progress_str = self._progress_str(*args, **kwargs)
self._output.append(progress_str)
reporter = TestReporter()
analysis = tune.run(test, num_samples=3, progress_reporter=reporter, verbose=3)
found = {k: False for k in test_result}
for output in reporter._output:
for key in test_result:
if key in output:
found[key] = True
assert found["foo_result"]
assert found["baz_result"]
assert not found["bar_result"]
def testProgressStr(self):
trials = []
for i in range(5):
t = Mock()
if i == 0:
t.status = "TERMINATED"
elif i == 1:
t.status = "PENDING"
else:
t.status = "RUNNING"
t.trial_id = "%05d" % i
t.local_experiment_path = "/foo"
t.temporary_state = Mock()
t.temporary_state.location = "here"
t.config = {"a": i, "b": i * 2, "n": {"k": [i, 2 * i]}}
t.evaluated_params = {"a": i, "b": i * 2, "n/k/0": i, "n/k/1": 2 * i}
t.last_result = {
"config": {"a": i, "b": i * 2, "n": {"k": [i, 2 * i]}},
"metric_1": i / 2,
"metric_2": i / 4,
"nested": {"sub": i / 2},
}
t.__str__ = lambda self: self.trial_id
trials.append(t)
# One metric, two parameters
prog1 = _trial_progress_str(
trials, ["metric_1"], ["a", "b"], fmt="psql", max_rows=3, force_table=True
)
print(prog1)
assert prog1 == EXPECTED_RESULT_1
# No metric, all parameters
prog2 = _trial_progress_str(
trials, [], None, fmt="psql", max_rows=None, force_table=True
)
print(prog2)
assert prog2 == EXPECTED_RESULT_2
# Two metrics, one parameter, all with custom representation
prog3 = _trial_progress_str(
trials,
{"nested/sub": "NestSub", "metric_2": "Metric 2"},
{"a": "A"},
fmt="psql",
max_rows=3,
force_table=True,
)
print(prog3)
assert prog3 == EXPECTED_RESULT_3
# Current best trial
best1 = _best_trial_str(trials[1], "metric_1")
assert best1 == EXPECTED_BEST_1
def testBestTrialStr(self):
"""Assert that custom nested parameter columns are printed correctly"""
config = {"nested": {"conf": "nested_value"}, "toplevel": "toplevel_value"}
trial = Trial("", config=config, stub=True)
trial.run_metadata.last_result = {
"metric": 1,
"config": config,
"nested": {"metric": 2},
}
result = _best_trial_str(trial, "metric")
self.assertIn("nested_value", result)
result = _best_trial_str(trial, "metric", parameter_columns=["nested/conf"])
self.assertIn("nested_value", result)
# Test that this works with a nested metric
result = _best_trial_str(
trial, "nested/metric", parameter_columns=["nested/conf"]
)
self.assertIn("nested_value", result)
def testBestTrialZero(self):
trial1 = Trial("", config={}, stub=True)
trial1.run_metadata.last_result = {"metric": 7, "config": {}}
trial2 = Trial("", config={}, stub=True)
trial2.run_metadata.last_result = {"metric": 0, "config": {}}
trial3 = Trial("", config={}, stub=True)
trial3.run_metadata.last_result = {"metric": 2, "config": {}}
reporter = TuneReporterBase(metric="metric", mode="min")
best_trial, metric = reporter._current_best_trial([trial1, trial2, trial3])
assert best_trial == trial2
def testBestTrialNan(self):
trial1 = Trial("", config={}, stub=True)
trial1.run_metadata.last_result = {"metric": np.nan, "config": {}}
trial2 = Trial("", config={}, stub=True)
trial2.run_metadata.last_result = {"metric": 0, "config": {}}
trial3 = Trial("", config={}, stub=True)
trial3.run_metadata.last_result = {"metric": 2, "config": {}}
reporter = TuneReporterBase(metric="metric", mode="min")
best_trial, metric = reporter._current_best_trial([trial1, trial2, trial3])
assert best_trial == trial2
trial1 = Trial("", config={}, stub=True)
trial1.run_metadata.last_result = {"metric": np.nan, "config": {}}
trial2 = Trial("", config={}, stub=True)
trial2.run_metadata.last_result = {"metric": 0, "config": {}}
trial3 = Trial("", config={}, stub=True)
trial3.run_metadata.last_result = {"metric": 2, "config": {}}
reporter = TuneReporterBase(metric="metric", mode="max")
best_trial, metric = reporter._current_best_trial([trial1, trial2, trial3])
assert best_trial == trial3
def testTimeElapsed(self):
# Sun Feb 7 14:18:40 2016 -0800
# (time of the first Ray commit)
time_start = 1454825920
time_now = (
time_start
+ 1 * 60 * 60 # 1 hour
+ 31 * 60 # 31 minutes
+ 22 # 22 seconds
) # time to second commit
# Local timezone output can be tricky, so we don't check the
# day and the hour in this test.
output = _time_passed_str(time_start, time_now)
self.assertIn("Current time: 2016-02-", output)
self.assertIn(":50:02 (running for 01:31:22.00)", output)
time_now += 2 * 60 * 60 * 24 # plus two days
output = _time_passed_str(time_start, time_now)
self.assertIn("Current time: 2016-02-", output)
self.assertIn(":50:02 (running for 2 days, 01:31:22.00)", output)
def testCurrentBestTrial(self):
trials = []
for i in range(5):
t = Mock()
t.status = "RUNNING"
t.trial_id = "%05d" % i
t.local_experiment_path = "/foo"
t.temporary_state = Mock()
t.temporary_state.location = "here"
t.config = {"a": i, "b": i * 2, "n": {"k": [i, 2 * i]}}
t.evaluated_params = {"a": i}
t.last_result = {"config": {"a": i}, "metric_1": i / 2}
t.__str__ = lambda self: self.trial_id
trials.append(t)
class TestReporter(CLIReporter):
_output = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._max_report_freqency = 0
def report(self, *args, **kwargs):
progress_str = self._progress_str(*args, **kwargs)
self._output.append(progress_str)
reporter = TestReporter(mode="max")
reporter.report(trials, done=False)
assert EXPECTED_BEST_2 in reporter._output[0]
def testSortByMetric(self):
trials = []
for i in range(5):
t = Mock()
if i < 3:
t.status = "TERMINATED"
elif i == 3:
t.status = "PENDING"
else:
t.status = "RUNNING"
t.trial_id = "%05d" % i
t.local_experiment_path = "/foo"
t.temporary_state = Mock()
t.temporary_state.location = "here"
t.run_metadata = Mock()
t.config = {"a": i}
t.evaluated_params = {"a": i}
t.last_result = {"config": {"a": i}}
t.__str__ = lambda self: self.trial_id
trials.append(t)
# Set `metric_1` for terminated trails
trials[0].last_result["metric_1"] = 0.3
trials[0].last_result["nested"] = {"metric_2": 0.3}
trials[1].last_result["metric_1"] = 0.2
trials[1].last_result["nested"] = {"metric_2": 0.2}
trials[2].last_result["metric_1"] = 0.4
trials[2].last_result["nested"] = {"metric_2": 0.4}
class TestReporter(CLIReporter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._max_report_freqency = 0
self._output = ""
def report(self, *args, **kwargs):
progress_str = self._progress_str(*args, **kwargs)
self._output = progress_str
# Default reporter
reporter1 = TestReporter(max_progress_rows=4, mode="max", metric="metric_1")
reporter1.report(trials, done=False)
assert EXPECTED_SORT_RESULT_UNSORTED in reporter1._output
# Sort by metric (asc)
reporter2 = TestReporter(
max_progress_rows=4, mode="min", metric="metric_1", sort_by_metric=True
)
reporter2.report(trials, done=False)
assert EXPECTED_SORT_RESULT_ASC in reporter2._output
# Sort by metric (desc)
reporter3 = TestReporter(
max_progress_rows=4, mode="max", metric="metric_1", sort_by_metric=True
)
reporter3.report(trials, done=False)
assert EXPECTED_SORT_RESULT_DESC in reporter3._output
# Sort by metric when mode is None
reporter4 = TestReporter(
max_progress_rows=4, metric="metric_1", sort_by_metric=True
)
reporter4.report(trials, done=False)
assert EXPECTED_SORT_RESULT_UNSORTED in reporter4._output
# Sort by metric when metric is None
reporter5 = TestReporter(max_progress_rows=4, mode="max", sort_by_metric=True)
reporter5.report(trials, done=False)
assert EXPECTED_SORT_RESULT_UNSORTED in reporter5._output
# Sort by metric when metric is passed using
# reporter.setup (called from tune.run)
# calling repoter.set_search_properties
reporter6 = TestReporter(max_progress_rows=4, sort_by_metric=True)
reporter6.set_search_properties(metric="metric_1", mode="max")
reporter6.report(trials, done=False)
assert EXPECTED_SORT_RESULT_DESC in reporter6._output
# Sort by nested metric (asc)
reporter7 = TestReporter(
max_progress_rows=4,
mode="min",
metric="nested/metric_2",
sort_by_metric=True,
metric_columns=["nested/metric_2"],
)
reporter7.report(trials, done=False)
assert EXPECTED_NESTED_SORT_RESULT in reporter7._output
def testEndToEndReporting(self):
try:
os.environ["_TEST_TUNE_TRIAL_UUID"] = "xxxxx"
os.environ["TUNE_MAX_PENDING_TRIALS_PG"] = "100"
output = run_string_as_driver(END_TO_END_COMMAND)
try:
# New execution path is too fast, trials are already terminated
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
assert EXPECTED_END_TO_END_START in output
assert EXPECTED_END_TO_END_END in output
for line in output.splitlines():
if "(raylet)" in line:
assert "Setting" in line, "Unexpected raylet log messages"
except Exception:
print("*** BEGIN OUTPUT ***")
print(output)
print("*** END OUTPUT ***")
raise
finally:
del os.environ["_TEST_TUNE_TRIAL_UUID"]
def testVerboseReporting(self):
try:
os.environ["_TEST_TUNE_TRIAL_UUID"] = "xxxxx"
verbose_0_cmd = VERBOSE_CMD + "verbose=0)"
output = run_string_as_driver(verbose_0_cmd)
try:
self.assertNotIn(VERBOSE_EXP_OUT_1, output)
self.assertNotIn(VERBOSE_EXP_OUT_2, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_1, output)
self.assertIsNone(re.search(VERBOSE_TRIAL_NORM_2_PATTERN, output))
self.assertNotIn(VERBOSE_TRIAL_NORM_3, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_4, output)
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertNotIn(VERBOSE_TRIAL_DETAIL, output)
except Exception:
print("*** BEGIN OUTPUT ***")
print(output)
print("*** END OUTPUT ***")
raise
verbose_1_cmd = VERBOSE_CMD + "verbose=1)"
output = run_string_as_driver(verbose_1_cmd)
try:
# New execution path is too fast, trials are already terminated
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertIn(VERBOSE_EXP_OUT_1, output)
self.assertIn(VERBOSE_EXP_OUT_2, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_1, output)
self.assertIsNone(re.search(VERBOSE_TRIAL_NORM_2_PATTERN, output))
self.assertNotIn(VERBOSE_TRIAL_NORM_3, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_4, output)
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertNotIn(VERBOSE_TRIAL_DETAIL, output)
except Exception:
print("*** BEGIN OUTPUT ***")
print(output)
print("*** END OUTPUT ***")
raise
verbose_2_cmd = VERBOSE_CMD + "verbose=2)"
output = run_string_as_driver(verbose_2_cmd)
try:
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertIn(VERBOSE_EXP_OUT_1, output)
self.assertIn(VERBOSE_EXP_OUT_2, output)
self.assertIn(VERBOSE_TRIAL_NORM_1, output)
self.assertIsNotNone(re.search(VERBOSE_TRIAL_NORM_2_PATTERN, output))
self.assertIn(VERBOSE_TRIAL_NORM_3, output)
self.assertIn(VERBOSE_TRIAL_NORM_4, output)
self.assertNotIn(VERBOSE_TRIAL_DETAIL, output)
except Exception:
print("*** BEGIN OUTPUT ***")
print(output)
print("*** END OUTPUT ***")
raise
verbose_3_cmd = VERBOSE_CMD + "verbose=3)"
output = run_string_as_driver(verbose_3_cmd)
try:
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertIn(VERBOSE_EXP_OUT_1, output)
self.assertIn(VERBOSE_EXP_OUT_2, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_1, output)
self.assertIsNone(re.search(VERBOSE_TRIAL_NORM_2_PATTERN, output))
self.assertNotIn(VERBOSE_TRIAL_NORM_3, output)
self.assertNotIn(VERBOSE_TRIAL_NORM_4, output)
if os.environ.get("TUNE_NEW_EXECUTION") == "0":
self.assertIn(VERBOSE_TRIAL_DETAIL, output)
# Check that we don't print duplicate results at the end
self.assertTrue(output.count(VERBOSE_TRIAL_WITH_ONCE_RESULT) == 1)
self.assertIn(VERBOSE_TRIAL_WITH_ONCE_COMPLETED, output)
except Exception:
print("*** BEGIN OUTPUT ***")
print(output)
print("*** END OUTPUT ***")
raise
finally:
del os.environ["_TEST_TUNE_TRIAL_UUID"]
def testReporterDetection(self):
"""Test if correct reporter is returned from ``detect_reporter()``"""
reporter = _detect_reporter()
self.assertTrue(isinstance(reporter, CLIReporter))
self.assertFalse(isinstance(reporter, JupyterNotebookReporter))
with patch("ray.tune.progress_reporter.IS_NOTEBOOK", True):
reporter = _detect_reporter()
self.assertFalse(isinstance(reporter, CLIReporter))
self.assertTrue(isinstance(reporter, JupyterNotebookReporter))
trainer_reporter = _detect_reporter(_trainer_api=True)
self.assertFalse(isinstance(trainer_reporter, JupyterNotebookReporter))
self.assertTrue(isinstance(trainer_reporter, CLIReporter))
def testProgressReporterAPI(self):
class CustomReporter(ProgressReporter):
def should_report(self, trials, done=False):
return True
def report(self, trials, done, *sys_info):
pass
tune.run(
lambda config: 2,
num_samples=1,
progress_reporter=CustomReporter(),
verbose=3,
)
def testMaxLen(self):
trials = []
for i in range(5):
t = Mock()
t.status = "TERMINATED"
t.trial_id = "%05d" % i
t.local_experiment_path = "/foo"
t.temporary_state = Mock()
t.temporary_state.location = "here"
t.config = {"verylong" * 20: i}
t.evaluated_params = {"verylong" * 20: i}
t.last_result = {"some_metric": "evenlonger" * 100}
t.__str__ = lambda self: self.trial_id
trials.append(t)
progress_str = _trial_progress_str(
trials, metric_columns=["some_metric"], force_table=True
)
assert any(len(row) <= 90 for row in progress_str.split("\n"))
def test_max_len():
assert (
_max_len("some_long_string/even_longer", max_len=28)
== "some_long_string/even_longer"
)
assert _max_len("some_long_string/even_longer", max_len=15) == ".../even_longer"
assert (
_max_len(
"19_character_string/19_character_string/too_long", max_len=20, wrap=True
)
== "...r_string/19_chara\ncter_string/too_long"
)
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))