Skip to content
Merged
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
13 changes: 12 additions & 1 deletion gittensor/cli/miner_commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,18 @@ def _require_validator_axons(
metagraph, min_vtrust=min_vtrust, min_stake=min_stake
)
if not validator_axons:
_error('No reachable validator axons found on the network.', json_mode)
if excluded:
msg = (
f'No validators passed --min-vtrust={min_vtrust:g} / '
f'--min-stake={min_stake:,.0f} α; all {len(excluded)} candidate(s) excluded.'
)
if json_mode:
click.echo(json.dumps({'success': False, 'error': msg, 'skipped': excluded}))
else:
_render_skipped_validators(excluded, json_mode)
console.print(f'[red]Error: {msg}[/red]')
else:
_error('No reachable validator axons found on the network.', json_mode)
sys.exit(1)
return validator_axons, validator_uids, excluded

Expand Down
62 changes: 62 additions & 0 deletions tests/cli/test_miner_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
_pat_check_aggregate_counts,
_pat_post_aggregate_counts,
_pat_post_row_category,
_require_validator_axons,
)


Expand Down Expand Up @@ -184,6 +185,67 @@ def test_combines_reasons_when_both_fail(self):
assert len(excluded[0]['reasons']) == 2


class TestRequireValidatorAxonsErrorPath:
"""Regression: the error path must surface the same `excluded` payload the
success path renders, so operators see which threshold eliminated each UID."""

def test_filtered_json_envelope_includes_skipped_array(self, capsys):
mg = _fake_metagraph(
[
(0.99, True, 5_000.0),
(0.85, True, 3_000.0),
(0.72, True, 8_000.0),
]
)
with pytest.raises(SystemExit) as exc_info:
_require_validator_axons(mg, True, min_vtrust=0.25, min_stake=15_000.0)
assert exc_info.value.code == 1
payload = json.loads(capsys.readouterr().out.strip())
assert payload['success'] is False
assert '--min-stake' in payload['error']
assert '--min-vtrust' in payload['error']
assert len(payload['skipped']) == 3
assert {entry['uid'] for entry in payload['skipped']} == {0, 1, 2}
assert all(entry['reasons'] for entry in payload['skipped'])

def test_filtered_tty_renders_skipped_table_and_error(self, capsys):
mg = _fake_metagraph(
[
(0.99, True, 5_000.0),
(0.85, False, 50_000.0),
]
)
with pytest.raises(SystemExit) as exc_info:
_require_validator_axons(mg, False, min_vtrust=0.25, min_stake=15_000.0)
assert exc_info.value.code == 1
out = capsys.readouterr().out
assert 'Skipped Validators' in out
assert 'No validators passed' in out
assert 'Error:' in out

def test_truly_empty_metagraph_keeps_generic_message(self, capsys):
mg = _fake_metagraph([])
with pytest.raises(SystemExit) as exc_info:
_require_validator_axons(mg, True, min_vtrust=0.25, min_stake=15_000.0)
assert exc_info.value.code == 1
payload = json.loads(capsys.readouterr().out.strip())
assert payload == {
'success': False,
'error': 'No reachable validator axons found on the network.',
}

def test_subvtrust_only_metagraph_keeps_generic_message(self, capsys):
# Sub-vtrust UIDs are dropped silently and never enter `excluded`,
# so the message should remain the generic one — not the threshold one.
mg = _fake_metagraph([(0.10, True, 50_000.0), (0.05, True, 100_000.0)])
with pytest.raises(SystemExit) as exc_info:
_require_validator_axons(mg, True, min_vtrust=0.25, min_stake=15_000.0)
assert exc_info.value.code == 1
payload = json.loads(capsys.readouterr().out.strip())
assert payload['error'] == 'No reachable validator axons found on the network.'
assert 'skipped' not in payload


class TestPatCheckAggregateCounts:
def test_splits_valid_no_pat_invalid_and_no_response(self):
results = [
Expand Down
Loading