1+ #!/usr/bin/env python3
2+ """
3+ Post network test instructions to PR comments.
4+ """
5+
6+ import os
7+ import sys
8+ import requests
9+ import json
10+ import yaml
11+ from typing import Optional , Dict , Any , List
12+
13+
14+ def get_github_context () -> Dict [str , Any ]:
15+ """Get GitHub context from environment variables."""
16+ github_token = os .getenv ('GITHUB_TOKEN' )
17+ github_repository = os .getenv ('GITHUB_REPOSITORY' )
18+ github_event_path = os .getenv ('GITHUB_EVENT_PATH' )
19+
20+ if not all ([github_token , github_repository , github_event_path ]):
21+ raise ValueError ("Missing required GitHub environment variables" )
22+
23+ with open (github_event_path , 'r' ) as f :
24+ event_data = json .load (f )
25+
26+ return {
27+ 'token' : github_token ,
28+ 'repository' : github_repository ,
29+ 'event' : event_data
30+ }
31+
32+
33+ def get_pr_number (event_data : Dict [str , Any ]) -> int :
34+ """Extract PR number from GitHub event data."""
35+ if 'pull_request' in event_data :
36+ return event_data ['pull_request' ]['number' ]
37+ elif 'number' in event_data :
38+ return event_data ['number' ]
39+ else :
40+ raise ValueError ("Unable to determine PR number from event data" )
41+
42+
43+ def get_test_names_from_yaml () -> List [str ]:
44+ """Extract test names from network-tests.yml workflow file."""
45+ try :
46+ workflow_path = os .path .join (os .path .dirname (__file__ ), '..' , 'workflows' , 'network-tests.yml' )
47+ with open (workflow_path , 'r' ) as f :
48+ workflow_data = yaml .safe_load (f )
49+
50+ matrix_include = workflow_data .get ('jobs' , {}).get ('network-tests' , {}).get ('strategy' , {}).get ('matrix' , {}).get ('include' , [])
51+ test_names = [item .get ('test_name' ) for item in matrix_include if item .get ('test_name' )]
52+
53+ if not test_names :
54+ # Fallback to hardcoded list if parsing fails
55+ return ['destroyable' , 'ping-pong' , 'one-to-many-internal-messages' , 'fq-deploy' , 'nft-index' ]
56+
57+ return test_names
58+ except Exception as e :
59+ print (f"Warning: Could not parse test names from YAML, using fallback: { e } " , file = sys .stderr )
60+ # Fallback to hardcoded list if parsing fails
61+ return ['destroyable' , 'ping-pong' , 'one-to-many-internal-messages' , 'fq-deploy' , 'nft-index' ]
62+
63+
64+ def create_comment_body (pr_number : int ) -> str :
65+ """Create the comment body with network test instructions."""
66+ test_names = get_test_names_from_yaml ()
67+ test_types_str = ', ' .join (f'`{ name } `' for name in test_names )
68+
69+ return f"""## 🧪 Network Tests
70+
71+ To run network tests for this PR, use:
72+ ```bash
73+ gh workflow run network-tests.yml -f pr_number={ pr_number }
74+ ```
75+
76+ **Available test options:**
77+ - Run all tests: `gh workflow run network-tests.yml -f pr_number={ pr_number } `
78+ - Run specific test: `gh workflow run network-tests.yml -f pr_number={ pr_number } -f test_selection=ping-pong`
79+
80+ **Test types:** { test_types_str }
81+
82+ Results will be posted as workflow runs in the Actions tab."""
83+
84+
85+ def find_existing_comment (github_token : str , repo : str , pr_number : int ) -> Optional [int ]:
86+ """Find existing network test instructions comment."""
87+ url = f"https://api.github.com/repos/{ repo } /issues/{ pr_number } /comments"
88+ headers = {
89+ 'Authorization' : f'token { github_token } ' ,
90+ 'Accept' : 'application/vnd.github.v3+json'
91+ }
92+
93+ response = requests .get (url , headers = headers )
94+ response .raise_for_status ()
95+
96+ comments = response .json ()
97+
98+ for comment in comments :
99+ if (comment .get ('user' , {}).get ('type' ) == 'Bot' and
100+ '🧪 Network Tests' in comment .get ('body' , '' )):
101+ return comment ['id' ]
102+
103+ return None
104+
105+
106+ def post_or_update_comment (github_token : str , repo : str , pr_number : int , body : str ) -> None :
107+ """Post new comment or update existing one."""
108+ headers = {
109+ 'Authorization' : f'token { github_token } ' ,
110+ 'Accept' : 'application/vnd.github.v3+json'
111+ }
112+
113+ existing_comment_id = find_existing_comment (github_token , repo , pr_number )
114+
115+ if existing_comment_id :
116+ # Update existing comment
117+ url = f"https://api.github.com/repos/{ repo } /issues/comments/{ existing_comment_id } "
118+ data = {'body' : body }
119+ response = requests .patch (url , headers = headers , json = data )
120+ print (f"Updated existing comment { existing_comment_id } " )
121+ else :
122+ # Create new comment
123+ url = f"https://api.github.com/repos/{ repo } /issues/{ pr_number } /comments"
124+ data = {'body' : body }
125+ response = requests .post (url , headers = headers , json = data )
126+ print (f"Created new comment on PR { pr_number } " )
127+
128+ response .raise_for_status ()
129+
130+
131+ def main ():
132+ """Main function."""
133+ try :
134+ context = get_github_context ()
135+ pr_number = get_pr_number (context ['event' ])
136+
137+ print (f"Processing PR #{ pr_number } " )
138+
139+ comment_body = create_comment_body (pr_number )
140+ post_or_update_comment (
141+ context ['token' ],
142+ context ['repository' ],
143+ pr_number ,
144+ comment_body
145+ )
146+
147+ print ("Successfully posted network test instructions" )
148+
149+ except Exception as e :
150+ print (f"Error: { e } " , file = sys .stderr )
151+ sys .exit (1 )
152+
153+
154+ if __name__ == '__main__' :
155+ main ()
0 commit comments