@@ -24,21 +24,129 @@ class DiagAction(Enum):
2424 DUMP_HEAP = "dump_heap"
2525
2626
27+ async def _analyze_temp_bugreport (serial : str , ctx : Context , actual_output_path : str , file_size_mb : float ) -> str :
28+ """Analyzes a temporary bug report file and returns a summary."""
29+ await ctx .info ("Analyzing bug report contents (summary)..." )
30+ # Using a raw string for the unzip command pattern to avoid issues with backslashes
31+ zip_list_cmd = f'unzip -l "{ actual_output_path } "'
32+ proc = await asyncio .create_subprocess_shell (
33+ zip_list_cmd ,
34+ stdout = asyncio .subprocess .PIPE ,
35+ stderr = asyncio .subprocess .PIPE ,
36+ )
37+ zip_stdout , zip_stderr = await proc .communicate ()
38+
39+ if proc .returncode != 0 :
40+ await ctx .warning (f"Could not list contents of temporary bugreport zip: { zip_stderr .decode ().strip ()} " )
41+ zip_contents_summary = "(Could not retrieve zip contents)"
42+ else :
43+ zip_contents_summary = zip_stdout .decode ().strip ()
44+
45+ summary = [
46+ f"# Bug Report for { serial } " ,
47+ f"Bug report ({ file_size_mb :.2f} MB) was processed from a temporary location." ,
48+ f"Original temporary path: `{ actual_output_path } `" ,
49+ "" ,
50+ "## Bug Report Contents (Summary)" ,
51+ "```" ,
52+ zip_contents_summary [:2000 ] + ("..." if len (zip_contents_summary ) > 2000 else "" ),
53+ "```" ,
54+ "" ,
55+ "Note: The temporary file has been cleaned up. If you need to keep the bug report, specify an 'output_path'." ,
56+ ]
57+ return "\\ n" .join (summary )
58+
59+
60+ async def _execute_bugreport_core (
61+ device : Device ,
62+ serial : str ,
63+ ctx : Context ,
64+ bugreport_cmd_arg : str ,
65+ actual_output_path : str ,
66+ timeout_seconds : int ,
67+ is_temporary : bool ,
68+ ) -> str :
69+ """Core logic for executing adb bugreport, handling results, and optionally summarizing."""
70+ await ctx .info (f"Running command: adb -s { serial } { bugreport_cmd_arg } { actual_output_path } " )
71+ process = None # Define process to ensure it's available in except block
72+ try :
73+ process = await asyncio .create_subprocess_exec (
74+ "adb" ,
75+ "-s" ,
76+ serial ,
77+ bugreport_cmd_arg ,
78+ actual_output_path ,
79+ stdout = asyncio .subprocess .PIPE ,
80+ stderr = asyncio .subprocess .PIPE ,
81+ )
82+
83+ stdout , stderr = await asyncio .wait_for (process .communicate (), timeout = timeout_seconds )
84+ stderr_str = stderr .decode ().strip () if stderr else ""
85+
86+ if process .returncode != 0 :
87+ await ctx .error (f"Bug report failed with exit code { process .returncode } " )
88+ await ctx .error (f"Error: { stderr_str } " )
89+ return f"Failed to capture bug report: { stderr_str } "
90+
91+ if not os .path .exists (actual_output_path ):
92+ await ctx .error ("Bug report file was not created" )
93+ return "Failed to capture bug report: No output file was created."
94+
95+ file_size = os .path .getsize (actual_output_path )
96+ file_size_mb = file_size / (1024 * 1024 )
97+ await ctx .info (f"✅ Bug report captured successfully! File size: { file_size_mb :.2f} MB" )
98+
99+ if not is_temporary :
100+ return f"Bug report saved to: { actual_output_path } ({ file_size_mb :.2f} MB)"
101+
102+ # Analyze and summarize the temporary bug report
103+ return await _analyze_temp_bugreport (serial , ctx , actual_output_path , file_size_mb )
104+
105+ except TimeoutError :
106+ if process and process .returncode is None : # Process is still running
107+ try :
108+ process .terminate ()
109+ await asyncio .wait_for (process .wait (), timeout = 5 )
110+ except TimeoutError :
111+ process .kill ()
112+ except ProcessLookupError : # pragma: no cover
113+ pass # Process already ended
114+ except Exception : # pragma: no cover
115+ logger .exception ("Error terminating bugreport process during timeout." )
116+ await ctx .error (f"Bug report timed out after { timeout_seconds } seconds" )
117+ return f"Bug report capture timed out after { timeout_seconds } seconds. Try again with a longer timeout value."
118+
119+
120+ async def _execute_bugreport_temp (
121+ device : Device , serial : str , ctx : Context , bugreport_cmd_arg : str , timeout_seconds : int
122+ ) -> str :
123+ """Handles bug report capture to a temporary directory and summarizes."""
124+ timestamp = (await device .run_shell ("date +%Y%m%d_%H%M%S" )).strip ()
125+ with tempfile .TemporaryDirectory (prefix = "droidmind_bugreport_" ) as temp_dir_name :
126+ filename = f"bugreport_{ serial } _{ timestamp } .zip"
127+ actual_output_path = os .path .join (temp_dir_name , filename )
128+ await ctx .info (
129+ f"No output path specified; bug report will be summarized from temporary file: { actual_output_path } "
130+ )
131+
132+ return await _execute_bugreport_core (
133+ device , serial , ctx , bugreport_cmd_arg , actual_output_path , timeout_seconds , is_temporary = True
134+ )
135+ # temp_dir_name is cleaned up automatically by 'with' statement
136+
137+
27138async def _capture_bugreport_impl (
28139 serial : str , ctx : Context , output_path : str = "" , include_screenshots : bool = True , timeout_seconds : int = 300
29140) -> str :
30141 """Implementation for capturing a bug report from a device."""
31142 try :
32- # Get the device
33143 device = await get_device_manager ().get_device (serial )
34144 if not device :
35145 await ctx .error (f"Device { serial } not connected or not found." )
36146 return f"Error: Device { serial } not found."
37147
38- # Log the operation with medium risk level
39148 log_command_execution ("capture_bugreport" , RiskLevel .MEDIUM )
40149
41- # Inform the user
42150 await ctx .info (f"🔍 Capturing bug report from device { serial } ..." )
43151 await ctx .info ("This may take a few minutes depending on the device's state." )
44152
@@ -49,110 +157,18 @@ async def _capture_bugreport_impl(
49157 await ctx .info ("Excluding screenshots to reduce bug report size." )
50158 bugreport_cmd_arg = "bugreportz"
51159
52- # Create a temporary directory if no output path is specified
53- temp_dir_obj = None
54- actual_output_path = output_path
55-
56- if not actual_output_path :
57- temp_dir_obj = tempfile .TemporaryDirectory (prefix = "droidmind_bugreport_" )
58- actual_output_path = os .path .join (
59- temp_dir_obj .name , f"bugreport_{ serial } _{ await device .run_shell ('date +%Y%m%d_%H%M%S' )} .zip"
60- )
61- await ctx .info (f"No output path specified, saving to temporary file: { actual_output_path } " )
62- else :
63- # Ensure the output directory exists if a path is provided
64- os .makedirs (os .path .dirname (os .path .abspath (actual_output_path )), exist_ok = True )
65-
66- # Run the bugreport command and save to the specified path
67- await ctx .info (f"Running command: adb -s { serial } { bugreport_cmd_arg } { actual_output_path } " )
160+ if not output_path :
161+ # Scenario: Temporary file. This will use 'with' inside the helper.
162+ return await _execute_bugreport_temp (device , serial , ctx , bugreport_cmd_arg , timeout_seconds )
68163
69- process = await asyncio .create_subprocess_exec (
70- "adb" ,
71- "-s" ,
72- serial ,
73- bugreport_cmd_arg ,
74- actual_output_path ,
75- stdout = asyncio .subprocess .PIPE ,
76- stderr = asyncio .subprocess .PIPE ,
164+ # Scenario: User-specified file.
165+ # Ensure the output directory exists if a path is provided
166+ abs_output_path = os .path .abspath (output_path )
167+ os .makedirs (os .path .dirname (abs_output_path ), exist_ok = True )
168+ return await _execute_bugreport_core (
169+ device , serial , ctx , bugreport_cmd_arg , abs_output_path , timeout_seconds , is_temporary = False
77170 )
78171
79- # Set up a timeout
80- try :
81- stdout , stderr = await asyncio .wait_for (process .communicate (), timeout = timeout_seconds )
82- stderr_str = stderr .decode ().strip () if stderr else ""
83-
84- if process .returncode != 0 :
85- await ctx .error (f"Bug report failed with exit code { process .returncode } " )
86- await ctx .error (f"Error: { stderr_str } " )
87- if temp_dir_obj :
88- temp_dir_obj .cleanup ()
89- return f"Failed to capture bug report: { stderr_str } "
90-
91- if not os .path .exists (actual_output_path ):
92- await ctx .error ("Bug report file was not created" )
93- if temp_dir_obj :
94- temp_dir_obj .cleanup ()
95- return "Failed to capture bug report: No output file was created."
96-
97- file_size = os .path .getsize (actual_output_path )
98- file_size_mb = file_size / (1024 * 1024 )
99- await ctx .info (f"✅ Bug report captured successfully! File size: { file_size_mb :.2f} MB" )
100-
101- if not temp_dir_obj :
102- return f"Bug report saved to: { actual_output_path } ({ file_size_mb :.2f} MB)"
103-
104- # If we created a temp file, analyze the bug report and return a summary
105- await ctx .info ("Analyzing bug report contents (summary)..." )
106- zip_list_cmd = f'unzip -l \\ "{ actual_output_path } \\ "'
107- proc = await asyncio .create_subprocess_shell (
108- zip_list_cmd ,
109- stdout = asyncio .subprocess .PIPE ,
110- stderr = asyncio .subprocess .PIPE ,
111- )
112- zip_stdout , _ = await proc .communicate ()
113- zip_contents_summary = zip_stdout .decode ().strip ()
114-
115- summary = [
116- f"# Bug Report for { serial } " ,
117- f"Temporary file saved to: `{ actual_output_path } ` ({ file_size_mb :.2f} MB)" ,
118- "" ,
119- "## Bug Report Contents (Summary)" ,
120- "```" ,
121- zip_contents_summary [:2000 ] + ("..." if len (zip_contents_summary ) > 2000 else "" ),
122- "```" ,
123- "" ,
124- "To extract specific information from this bug report, you can use:" ,
125- f'* `unzip -o \\ "{ actual_output_path } \\ " -d <extract_dir>` to extract all files' ,
126- (
127- f'* `unzip -o \\ "{ actual_output_path } \\ " bugreport-*.txt -d <extract_dir>` '
128- "to extract just the main report"
129- ),
130- "" ,
131- "Note: This is a temporary file and will be cleaned up." ,
132- ]
133- final_summary = "\\ n" .join (summary )
134- temp_dir_obj .cleanup ()
135- return final_summary
136-
137- except TimeoutError :
138- if process and process .returncode is None :
139- try :
140- process .terminate ()
141- await asyncio .wait_for (process .wait (), timeout = 5 )
142- except TimeoutError :
143- process .kill ()
144- except ProcessLookupError :
145- pass
146- await ctx .error (f"Bug report timed out after { timeout_seconds } seconds" )
147- if temp_dir_obj :
148- temp_dir_obj .cleanup ()
149- return (
150- f"Bug report capture timed out after { timeout_seconds } seconds. Try again with a longer timeout value."
151- )
152- finally :
153- if temp_dir_obj and os .path .exists (temp_dir_obj .name ):
154- temp_dir_obj .cleanup ()
155-
156172 except Exception as e :
157173 logger .exception ("Error capturing bug report: %s" , e )
158174 await ctx .error (f"Error capturing bug report: { e } " )
0 commit comments