Skip to content
Closed
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
34 changes: 34 additions & 0 deletions gittensor/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Forward-reference only — avoids importing the mirror subpackage at runtime
# and prevents accidental coupling. The mirror_* lists below are typed as
# strings to defer resolution.
from gittensor.utils.mirror.models import MirrorLinkedIssue, MirrorPullRequest
from gittensor.validator.oss_contributions.mirror.scored_pr import ScoredMirrorPR

from gittensor.constants import (
Expand Down Expand Up @@ -708,6 +709,9 @@ def _build_cache_entry(evaluation: 'MinerEvaluation') -> 'MinerEvaluation':
cached.merged_pull_requests = [_pr_for_cache(pr) for pr in evaluation.merged_pull_requests]
cached.open_pull_requests = [_pr_for_cache(pr) for pr in evaluation.open_pull_requests]
cached.closed_pull_requests = [_pr_for_cache(pr) for pr in evaluation.closed_pull_requests]
cached.mirror_merged_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_merged_prs]
cached.mirror_open_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_open_prs]
cached.mirror_closed_prs = [_scored_mirror_pr_for_cache(s) for s in evaluation.mirror_closed_prs]
return cached

@staticmethod
Expand All @@ -720,6 +724,9 @@ def _isolate_for_downstream(cached_eval: 'MinerEvaluation') -> 'MinerEvaluation'
copy_eval.merged_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.merged_pull_requests]
copy_eval.open_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.open_pull_requests]
copy_eval.closed_pull_requests = [_pr_with_fresh_issues(pr) for pr in cached_eval.closed_pull_requests]
copy_eval.mirror_merged_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_merged_prs]
copy_eval.mirror_open_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_open_prs]
copy_eval.mirror_closed_prs = [_scored_mirror_pr_isolated(s) for s in cached_eval.mirror_closed_prs]
return copy_eval


Expand All @@ -735,3 +742,30 @@ def _pr_with_fresh_issues(pr: 'PullRequest') -> 'PullRequest':
if pr.issues is not None:
pr_copy.issues = [copy.copy(issue) for issue in pr.issues]
return pr_copy


def _mirror_linked_issue_copy(issue: 'MirrorLinkedIssue') -> 'MirrorLinkedIssue':
issue_copy = copy.copy(issue)
issue_copy.labels = [copy.copy(label) for label in issue.labels]
return issue_copy


def _mirror_pr_copy(pr: 'MirrorPullRequest') -> 'MirrorPullRequest':
pr_copy = copy.copy(pr)
pr_copy.review_summary = copy.copy(pr.review_summary)
pr_copy.labels = [copy.copy(label) for label in pr.labels]
pr_copy.linked_issues = [_mirror_linked_issue_copy(li) for li in pr.linked_issues]
return pr_copy


def _scored_mirror_pr_for_cache(scored: 'ScoredMirrorPR') -> 'ScoredMirrorPR':
scored_copy = copy.copy(scored)
scored_copy.pr = _mirror_pr_copy(scored.pr)
scored_copy.files = None
return scored_copy


def _scored_mirror_pr_isolated(scored: 'ScoredMirrorPR') -> 'ScoredMirrorPR':
scored_copy = copy.copy(scored)
scored_copy.pr = _mirror_pr_copy(scored.pr)
return scored_copy
125 changes: 125 additions & 0 deletions tests/validator/test_validator_cache_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import cast

from gittensor.classes import FileChange, Issue, MinerEvaluation, MinerEvaluationCache, PRState, PullRequest
from gittensor.utils.mirror.models import MirrorFile, MirrorLinkedIssue, MirrorPullRequest, MirrorReviewSummary
from gittensor.validator.oss_contributions.mirror.scored_pr import ScoredMirrorPR
from neurons.validator import Validator


Expand Down Expand Up @@ -175,3 +177,126 @@ def test_store_isolates_cache_from_source_issue_mutations(self):
cached_issues = cached.merged_pull_requests[0].issues
assert cached_issues is not None
assert cached_issues[0].discovery_earned_score == 0.0


def _make_scored_mirror_pr(pr_number: int = 100, blob: str = 'heavy file content') -> ScoredMirrorPR:
now = datetime.now(timezone.utc)
linked_issue = MirrorLinkedIssue(
number=77,
title='mirror linked issue',
state='CLOSED',
state_reason='COMPLETED',
author_github_id='99',
author_association='CONTRIBUTOR',
created_at=now,
closed_at=now,
updated_at=now,
is_transferred=False,
solved_by_pr=pr_number,
)
pr = MirrorPullRequest(
repo_full_name='owner/repo',
pr_number=pr_number,
title='mirror pr',
body=None,
state='MERGED',
author_github_id='12345',
author_login='miner',
author_association='CONTRIBUTOR',
created_at=now,
closed_at=None,
merged_at=now,
last_edited_at=None,
edited_after_merge=False,
hours_since_merge=0.0,
merged_by_login='maintainer',
base_ref='main',
head_ref='feature',
head_repo_full_name='owner/repo',
default_branch='main',
head_sha='head',
base_sha='base',
merge_base_sha='merge-base',
additions=3,
deletions=1,
commits_count=1,
scoring_data_stored=True,
review_summary=MirrorReviewSummary(maintainer_changes_requested_count=1),
linked_issues=[linked_issue],
)
scored = ScoredMirrorPR(pr=pr, base_score=8.0, token_score=12.0)
scored.files = [
MirrorFile(
filename='src/lib.py',
previous_filename=None,
status='modified',
additions=3,
deletions=1,
changes=4,
is_binary=False,
head_content=blob,
base_content=blob,
)
]
return scored


def _build_mirror_eval() -> MinerEvaluation:
ev = MinerEvaluation(uid=1, hotkey='hotkey_1', github_id='12345')
ev.mirror_merged_prs = [_make_scored_mirror_pr()]
return ev


class TestMirrorCacheIsolation:
"""Mirror-PR counterpart to TestCacheIsolation: validate that
ScoredMirrorPR.files are dropped, and mutable mirror sub-objects
(pr, review_summary, labels, linked_issues) are isolated."""

def test_cache_drops_mirror_files(self):
cache = MinerEvaluationCache()
source = _build_mirror_eval()

cache.store(source)

# Source files must remain intact.
assert source.mirror_merged_prs[0].files is not None
assert source.mirror_merged_prs[0].files[0].head_content == 'heavy file content'

cached = cache.get(uid=1, hotkey='hotkey_1', github_id='12345')
assert cached is not None
assert cached.mirror_merged_prs[0].files is None

def test_store_isolates_mirror_fields_from_source_mutations(self):
cache = MinerEvaluationCache()
source = _build_mirror_eval()
cache.store(source)

source.mirror_merged_prs[0].base_score = 999.0
source.mirror_merged_prs[0].pr.title = 'mutated'
source.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count = 99
source.mirror_merged_prs[0].pr.linked_issues[0].title = 'mutated issue'

cached = cache.get(uid=1, hotkey='hotkey_1', github_id='12345')
assert cached is not None
assert cached.mirror_merged_prs[0].base_score == 8.0
assert cached.mirror_merged_prs[0].pr.title == 'mirror pr'
assert cached.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count == 1
assert cached.mirror_merged_prs[0].pr.linked_issues[0].title == 'mirror linked issue'

def test_get_returns_isolated_mirror_copies(self):
cache = MinerEvaluationCache()
cache.store(_build_mirror_eval())

first = cache.get(uid=1, hotkey='hotkey_1', github_id='12345')
assert first is not None
first.mirror_merged_prs[0].base_score = 999.0
first.mirror_merged_prs[0].pr.title = 'mutated'
first.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count = 99
first.mirror_merged_prs[0].pr.linked_issues[0].title = 'mutated issue'

second = cache.get(uid=1, hotkey='hotkey_1', github_id='12345')
assert second is not None
assert second.mirror_merged_prs[0].base_score == 8.0
assert second.mirror_merged_prs[0].pr.title == 'mirror pr'
assert second.mirror_merged_prs[0].pr.review_summary.maintainer_changes_requested_count == 1
assert second.mirror_merged_prs[0].pr.linked_issues[0].title == 'mirror linked issue'
Loading