Skip to content

Commit 6ddfbe2

Browse files
authored
Merge branch 'test' into fix/pat-synapse-repr-leak-850
2 parents ad56c3f + 58036fd commit 6ddfbe2

9 files changed

Lines changed: 88 additions & 11 deletions

File tree

‎gittensor/classes.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def is_test_file(self) -> bool:
8383
r'(^|/)androidtest[a-z]*/',
8484
r'(^|/)integrationtest/',
8585
r'(^|/)spec/',
86+
r'\.tests?/', # .NET MyProject.Tests/FooTests.cs
8687
]
8788
if any(re.search(pattern, filename_lower) for pattern in test_dir_patterns):
8889
return True
@@ -98,6 +99,7 @@ def is_test_file(self) -> bool:
9899
r'\.spec\.[^.]+$',
99100
r'^test\.[^.]+$',
100101
r'^tests\.[^.]+$',
102+
r'^conftest\.py$',
101103
]
102104

103105
return any(re.search(pattern, basename) for pattern in test_patterns)

‎gittensor/cli/issue_commands/helpers.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,7 @@ def _read_issues_from_child_storage(substrate, contract_addr: str, verbose: bool
784784
console.print(f'[dim]Debug: next_issue_id from contract = {next_issue_id}[/dim]')
785785

786786
# Sanity check: next_issue_id should be reasonable (< 1 million for any real deployment)
787-
MAX_REASONABLE_ISSUE_ID = 1_000_000
788-
if next_issue_id > MAX_REASONABLE_ISSUE_ID:
787+
if next_issue_id > MAX_ISSUE_ID:
789788
console.print(f'[yellow]Warning: next_issue_id ({next_issue_id}) is unreasonably large.[/yellow]')
790789
console.print('[yellow]This may indicate a storage format mismatch. Check contract version.[/yellow]')
791790
return []

‎gittensor/cli/issue_commands/view.py‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
handle_exception,
3030
print_network_header,
3131
read_issues_from_contract,
32+
validate_issue_id,
3233
with_cli_behavior_options,
3334
with_network_contract_options,
3435
)
@@ -42,13 +43,22 @@
4243
type=int,
4344
help='View a specific issue by ID',
4445
)
46+
@click.option(
47+
'--repo',
48+
'repo_filter',
49+
default=None,
50+
type=str,
51+
help='Filter issues to a specific repository (owner/name).',
52+
)
4553
@with_cli_behavior_options(
4654
include_verbose=True,
4755
include_json=True,
4856
verbose_help='Show debug output for contract reads',
4957
)
5058
@with_network_contract_options('Contract address (uses default if empty)')
51-
def issues_list(issue_id: int, network: str, rpc_url: str, contract: str, verbose: bool, as_json: bool):
59+
def issues_list(
60+
issue_id: int, repo_filter: str, network: str, rpc_url: str, contract: str, verbose: bool, as_json: bool
61+
):
5262
"""List issues or view a specific issue.
5363
5464
[dim]Examples:
@@ -58,6 +68,12 @@ def issues_list(issue_id: int, network: str, rpc_url: str, contract: str, verbos
5868
$ gitt i list --json
5969
[/dim]
6070
"""
71+
if issue_id is not None:
72+
try:
73+
validate_issue_id(issue_id, 'id')
74+
except click.BadParameter as e:
75+
handle_exception(as_json, str(e), 'bad_parameter')
76+
6177
contract_addr, ws_endpoint, network_name = _resolve_contract_and_network(
6278
contract,
6379
network,
@@ -76,6 +92,11 @@ def issues_list(issue_id: int, network: str, rpc_url: str, contract: str, verbos
7692
for issue in issues:
7793
issue['bounty_alpha'] = format_alpha(issue.get('bounty_amount', 0), 4)
7894
issue['target_alpha'] = format_alpha(issue.get('target_bounty', 0), 4)
95+
96+
# Apply --repo filter before rendering (--id takes precedence)
97+
if repo_filter and issue_id is None:
98+
issues = [i for i in issues if i.get('repository_full_name', '').lower() == repo_filter.lower()]
99+
79100
if issue_id is not None:
80101
issue = next((i for i in issues if i['id'] == issue_id), None)
81102
if issue is None:
@@ -110,6 +131,10 @@ def issues_list(issue_id: int, network: str, rpc_url: str, contract: str, verbos
110131
handle_exception(as_json, f'Issue {issue_id} not found on-chain.', 'not_found')
111132
return
112133

134+
# Apply --repo filter before table render (--id takes precedence)
135+
if repo_filter and issue_id is None:
136+
issues = [i for i in issues if i.get('repository_full_name', '').lower() == repo_filter.lower()]
137+
113138
# Table view of all issues
114139
console.print('[bold cyan]Available Issues[/bold cyan]\n')
115140

‎gittensor/cli/main.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
import os
1717
import sys
1818

19-
# Stub heavy imports during shell completion so tab-completion stays fast.
20-
if os.environ.get('_GITT_COMPLETE'):
19+
# Stub heavy imports during shell completion and --help so tab-completion stays
20+
# fast and bittensor's argparse doesn't hijack click's help output.
21+
if os.environ.get('_GITT_COMPLETE') or any(arg in ('-h', '--help') for arg in sys.argv[1:]):
2122
import types as _types
2223

2324
class _Stub(_types.ModuleType):
@@ -27,7 +28,7 @@ def __getattr__(self, _name):
2728
def __call__(self, *_a, **_kw):
2829
return self
2930

30-
_stub = _Stub('_gitt_completion_stub')
31+
_stub = _Stub('_gitt_cli_stub')
3132
for _pkg in ('bittensor', 'requests'):
3233
sys.modules[_pkg] = _stub
3334

‎gittensor/utils/github_api_tools.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def get_merge_base_sha(repository: str, base_sha: str, head_sha: str, token: str
319319
return None
320320

321321

322-
def get_pull_request_file_changes(repository: str, pr_number: int, token: str) -> Optional[List[FileChange]]:
322+
def get_pull_request_file_changes(repository: str, pr_number: int, token: str) -> List[FileChange]:
323323
"""
324324
Get the diff for a specific PR by repository name and PR number.
325325
@@ -333,7 +333,7 @@ def get_pull_request_file_changes(repository: str, pr_number: int, token: str) -
333333
pr_number (int): PR number
334334
token (str): Github pat
335335
Returns:
336-
List[FileChanges]: List object with file changes or None if error
336+
List[FileChanges]: List object with file changes or empty list if error
337337
"""
338338
max_attempts = 3
339339
per_page = 100

‎gittensor/validator/issue_competitions/forward.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ async def issue_competitions(
136136
bt.logging.info(f'Voted cancel (solver {solver_github_id} not eligible): {issue_label}')
137137
continue
138138

139-
miner_coldkey = get_miner_coldkey(miner_hotkey, self.subtensor, self.config.netuid) # type: ignore[attr-defined]
139+
miner_coldkey = get_miner_coldkey(miner_hotkey, self.subtensor)
140140
if not miner_coldkey:
141141
bt.logging.warning(
142142
f'Could not get coldkey for hotkey {miner_hotkey} (solver {solver_github_id}): {issue_label}'

‎gittensor/validator/utils/issue_competitions.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
import bittensor as bt
99

1010

11-
def get_miner_coldkey(hotkey: str, subtensor: bt.Subtensor, netuid: int) -> Optional[str]:
11+
def get_miner_coldkey(hotkey: str, subtensor: bt.Subtensor) -> Optional[str]:
1212
"""
1313
Get the coldkey for a miner's hotkey.
1414
1515
Args:
1616
hotkey: Miner's hotkey address
1717
subtensor: Bittensor subtensor instance
18-
netuid: Network UID
1918
2019
Returns:
2120
Coldkey address or None

‎tests/cli/test_issues_list_json.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import json
77
from unittest.mock import patch
88

9+
import pytest
10+
911
FAKE_ISSUES = [
1012
{
1113
'id': 1,
@@ -51,3 +53,28 @@ def test_issues_list_human_missing_issue_exits_non_zero(cli_root, runner):
5153
assert result.exit_code != 0
5254
assert '999' in result.output
5355
assert 'not found' in result.output.lower()
56+
57+
58+
@pytest.mark.parametrize('bad_id', ['0', '-1', '1000000', '99999999999999'])
59+
def test_issues_list_rejects_invalid_id_human(cli_root, runner, bad_id):
60+
"""Out-of-range --id must be rejected at parse time without any contract read."""
61+
with patch('gittensor.cli.issue_commands.view.read_issues_from_contract') as mock_read:
62+
result = runner.invoke(cli_root, ['issues', 'list', '--id', bad_id], catch_exceptions=False)
63+
64+
assert result.exit_code != 0
65+
assert 'between 1 and 999999' in result.output
66+
mock_read.assert_not_called()
67+
68+
69+
@pytest.mark.parametrize('bad_id', ['0', '-1', '1000000'])
70+
def test_issues_list_rejects_invalid_id_json(cli_root, runner, bad_id):
71+
"""JSON mode must emit a structured bad_parameter error consistent with `submissions --id`."""
72+
with patch('gittensor.cli.issue_commands.view.read_issues_from_contract') as mock_read:
73+
result = runner.invoke(cli_root, ['issues', 'list', '--json', '--id', bad_id], catch_exceptions=False)
74+
75+
assert result.exit_code != 0
76+
payload = json.loads(result.output)
77+
assert payload['success'] is False
78+
assert payload['error']['type'] == 'bad_parameter'
79+
assert 'between 1 and 999999' in payload['error']['message']
80+
mock_read.assert_not_called()

‎tests/test_classes.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ def test_is_test_file_preserves_existing_test_conventions():
7272
assert _file_change('src/foo/bar.py').is_test_file() is False
7373

7474

75+
@pytest.mark.parametrize(
76+
'filename',
77+
[
78+
'src/MyProject.Tests/AccountServiceTests.cs',
79+
'src/MyProject.Tests/AccountServiceTest.cs',
80+
],
81+
)
82+
def test_is_test_file_detects_dotnet_dotted_tests_directory(filename):
83+
assert _file_change(filename).is_test_file() is True
84+
85+
86+
@pytest.mark.parametrize(
87+
'filename',
88+
[
89+
'conftest.py',
90+
'tests/conftest.py',
91+
'project/conftest.py',
92+
'project/sub/package/conftest.py',
93+
],
94+
)
95+
def test_is_test_file_detects_conftest_at_any_depth(filename):
96+
assert _file_change(filename).is_test_file() is True
97+
98+
7599
def test_pull_request_handles_deleted_label_event():
76100
pr_data = {
77101
'number': 42,

0 commit comments

Comments
 (0)