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
9 changes: 9 additions & 0 deletions gittensor/validator/issue_discovery/mirror_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,15 @@ async def _resolve_solving_pr_score(
)
return None

if not files_response.scoring_data_stored:
cache_stats.fetch_failures += 1
bt.logging.warning(
f'Mirror scoring data unavailable for solving PR #{solving_pr.pr_number} '
f'({issue.repo_full_name}): scoring_data_stored=False — '
f'issue #{issue.issue_number} not scored'
)
return None

file_changes, file_contents = mirror_files_to_legacy(
issue.repo_full_name, solving_pr.pr_number, files_response.files
)
Expand Down
84 changes: 84 additions & 0 deletions tests/validator/issue_discovery/test_mirror_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,90 @@ def test_resolve_increments_fetch_failures_on_request_error(self):
# Failed lookups are NOT cached (so a retry is possible)
assert cache == {}

def test_resolve_treats_unavailable_scoring_data_as_failure(self):
# scoring_data_stored=False is data-availability noise, not a real
# zero score. Same handling as MirrorRequestError: increment
# fetch_failures, return None, leave cache empty so a sibling miner's
# later lookup can retry within the cycle.
from gittensor.validator.issue_discovery.mirror_scan import (
_CacheStats,
_resolve_solving_pr_score,
)

client = Mock()
client.get_pr_files.return_value = MirrorPullRequestFilesResponse.from_dict(
{
'repo_full_name': 'entrius/gittensor-ui',
'pr_number': 100,
'head_sha': 'h',
'base_sha': 'b',
'merge_base_sha': 'mb',
'scoring_data_stored': False,
'files': [],
}
)
cache = {}
stats = _CacheStats()

issue = MirrorIssue.from_dict(_issue_dict())
result = asyncio.run(
_resolve_solving_pr_score(issue, issue.solving_pr, cache, stats, client, _EMPTY_LANGS, _EMPTY_TOKEN_CONFIG)
)

assert result is None
assert stats.misses == 1
assert stats.fetch_failures == 1
assert cache == {}

def test_unavailable_scoring_data_is_not_cached_across_sibling_lookups(self):
# Acceptance for issue #836: a single scoring_data_stored=False response
# feeding two issues that share the same solving PR (i.e. across two
# miners discovering the same PR) results in two misses, two fetch
# failures, and an empty cache. Without the fix, the first call would
# cache base_score=0 / token_score=0, the second would be a "hit" on
# that fabricated zero, and fetch_failures would never increment.
from gittensor.validator.issue_discovery.mirror_scan import (
_CacheStats,
_resolve_solving_pr_score,
)

client = Mock()
client.get_pr_files.return_value = MirrorPullRequestFilesResponse.from_dict(
{
'repo_full_name': 'entrius/gittensor-ui',
'pr_number': 100,
'head_sha': 'h',
'base_sha': 'b',
'merge_base_sha': 'mb',
'scoring_data_stored': False,
'files': [],
}
)
cache = {}
stats = _CacheStats()

issue_a = MirrorIssue.from_dict(_issue_dict(issue_number=50))
issue_b = MirrorIssue.from_dict(_issue_dict(issue_number=51))

result_a = asyncio.run(
_resolve_solving_pr_score(
issue_a, issue_a.solving_pr, cache, stats, client, _EMPTY_LANGS, _EMPTY_TOKEN_CONFIG
)
)
result_b = asyncio.run(
_resolve_solving_pr_score(
issue_b, issue_b.solving_pr, cache, stats, client, _EMPTY_LANGS, _EMPTY_TOKEN_CONFIG
)
)

assert result_a is None
assert result_b is None
assert stats.hits == 0
assert stats.misses == 2
assert stats.fetch_failures == 2
assert cache == {}
assert client.get_pr_files.call_count == 2


class TestOpenIssueSpamSourceIsMirror:
"""The open-issue spam multiplier sources its count from mirror's response,
Expand Down
Loading