Skip to content

Commit a8fe52b

Browse files
committed
added download_test
1 parent 939f698 commit a8fe52b

File tree

1 file changed

+389
-0
lines changed

1 file changed

+389
-0
lines changed

download_test/download_test.py

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
import subprocess
2+
import threading
3+
import re
4+
import time
5+
from datetime import datetime
6+
import xml.etree.ElementTree as ET
7+
import os
8+
9+
def get_connected_devices():
10+
result = subprocess.run(['adb', 'devices'], capture_output=True, text=True)
11+
lines = result.stdout.strip().split('\n')
12+
devices = []
13+
for line in lines[1:]:
14+
if line.strip():
15+
parts = line.split('\t')
16+
if len(parts) == 2 and parts[1] == 'device':
17+
devices.append(parts[0])
18+
return devices
19+
20+
def choose_device(devices):
21+
print("Multiple devices connected:")
22+
for idx, device in enumerate(devices):
23+
print(f"{idx + 1}. {device}")
24+
while True:
25+
try:
26+
choice = int(input("Select the device number to use: "))
27+
if 1 <= choice <= len(devices):
28+
return devices[choice - 1]
29+
else:
30+
print(f"Please enter a number between 1 and {len(devices)}.")
31+
except ValueError:
32+
print("Invalid input. Please enter a number.")
33+
34+
def start_browser_and_download(download_url, package_name, activity_name, device_id):
35+
adb_command = ['adb', 'shell', 'am', 'start', '-n', f'{package_name}/{activity_name}',
36+
'-a', 'android.intent.action.VIEW', '-d', download_url]
37+
if device_id:
38+
adb_command.insert(1, '-s')
39+
adb_command.insert(2, device_id)
40+
subprocess.run(adb_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
41+
time.sleep(5)
42+
43+
def dump_ui_hierarchy(device_id):
44+
if os.path.exists('ui_dump.xml'):
45+
os.remove('ui_dump.xml')
46+
adb_command_dump = ['adb', 'shell', 'uiautomator', 'dump', '/sdcard/ui_dump.xml']
47+
adb_command_pull = ['adb', 'pull', '/sdcard/ui_dump.xml']
48+
if device_id:
49+
adb_command_dump.insert(1, '-s')
50+
adb_command_dump.insert(2, device_id)
51+
adb_command_pull.insert(1, '-s')
52+
adb_command_pull.insert(2, device_id)
53+
subprocess.run(adb_command_dump, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
54+
subprocess.run(adb_command_pull, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
55+
56+
def find_element_bounds(xml_file, text=None, resource_id=None, content_desc=None, class_name=None):
57+
tree = ET.parse(xml_file)
58+
root = tree.getroot()
59+
for node in root.iter('node'):
60+
node_text = node.attrib.get('text')
61+
node_resource_id = node.attrib.get('resource-id')
62+
node_content_desc = node.attrib.get('content-desc')
63+
node_class = node.attrib.get('class')
64+
if resource_id and node_resource_id and resource_id == node_resource_id:
65+
bounds = node.attrib.get('bounds')
66+
return bounds
67+
if text and node_text and text.lower() in node_text.lower():
68+
if class_name and node_class != class_name:
69+
continue
70+
bounds = node.attrib.get('bounds')
71+
return bounds
72+
if content_desc and node_content_desc and content_desc.lower() in node_content_desc.lower():
73+
bounds = node.attrib.get('bounds')
74+
return bounds
75+
return None
76+
77+
def get_center_coordinates(bounds):
78+
match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds)
79+
if match:
80+
left, top, right, bottom = map(int, match.groups())
81+
center_x = (left + right) // 2
82+
center_y = (top + bottom) // 2
83+
return center_x, center_y
84+
else:
85+
return None
86+
87+
def tap_screen(x, y, device_id):
88+
adb_command = ['adb', 'shell', 'input', 'tap', str(x), str(y)]
89+
if device_id:
90+
adb_command.insert(1, '-s')
91+
adb_command.insert(2, device_id)
92+
subprocess.run(adb_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
93+
94+
def click_download_button(device_id):
95+
max_attempts = 5
96+
for attempt in range(max_attempts):
97+
dump_ui_hierarchy(device_id)
98+
bounds = find_element_bounds('ui_dump.xml', resource_id='org.mozilla.firefox:id/positive_button')
99+
if not bounds:
100+
bounds = find_element_bounds('ui_dump.xml', resource_id='android:id/button1')
101+
if not bounds:
102+
bounds = find_element_bounds('ui_dump.xml', text='Download', class_name='android.widget.Button')
103+
if not bounds:
104+
bounds = find_element_bounds('ui_dump.xml', text='Download')
105+
if bounds:
106+
coordinates = get_center_coordinates(bounds)
107+
if coordinates:
108+
x, y = coordinates
109+
tap_screen(x, y, device_id)
110+
print(f"Tapped on Download button at ({x}, {y}).")
111+
return True
112+
else:
113+
print(f"Download button not found. Attempt {attempt + 1}/{max_attempts}")
114+
time.sleep(2)
115+
print("Failed to find and tap the download button after multiple attempts.")
116+
return False
117+
118+
def parse_log_time(log_line):
119+
match = re.match(r'^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})', log_line)
120+
if match:
121+
timestamp_str = match.group(1)
122+
current_year = datetime.now().year
123+
timestamp_str_with_year = f"{current_year}-{timestamp_str}"
124+
try:
125+
timestamp = datetime.strptime(timestamp_str_with_year, '%Y-%m-%d %H:%M:%S.%f')
126+
return timestamp
127+
except ValueError as e:
128+
print(f"Failed to parse timestamp '{timestamp_str_with_year}': {e}")
129+
return None
130+
else:
131+
print(f"No timestamp found in log line: {log_line}")
132+
return None
133+
134+
def monitor_adb_logs(start_event, end_event, result_dict, device_id):
135+
adb_command = ['adb', 'logcat', '-v', 'time']
136+
if device_id:
137+
adb_command.insert(1, '-s')
138+
adb_command.insert(2, device_id)
139+
process = subprocess.Popen(adb_command,
140+
stdout=subprocess.PIPE,
141+
stderr=subprocess.STDOUT,
142+
universal_newlines=True,
143+
bufsize=1)
144+
start_pattern = re.compile(r'Open with FUSE\. FilePath: .*\.pending-.*')
145+
end_pattern = re.compile(r'Moving /storage/emulated/0/Download/\.pending-.* to /storage/emulated/0/Download/.*')
146+
for line in process.stdout:
147+
line = line.strip()
148+
if start_pattern.search(line):
149+
print(f"Download started: {line}")
150+
timestamp = parse_log_time(line)
151+
if timestamp:
152+
result_dict['start_time'] = timestamp
153+
start_event.set()
154+
elif end_pattern.search(line):
155+
print(f"Download finished: {line}")
156+
timestamp = parse_log_time(line)
157+
if timestamp:
158+
result_dict['end_time'] = timestamp
159+
end_event.set()
160+
break
161+
process.terminate()
162+
163+
def calculate_download_duration(start_time, end_time):
164+
duration = (end_time - start_time).total_seconds()
165+
return duration
166+
167+
def clear_adb_logs(device_id):
168+
adb_command = ['adb', 'logcat', '-c']
169+
if device_id:
170+
adb_command.insert(1, '-s')
171+
adb_command.insert(2, device_id)
172+
subprocess.run(adb_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
173+
174+
def close_browser(package_name, device_id):
175+
adb_command = ['adb', 'shell', 'am', 'force-stop', package_name]
176+
if device_id:
177+
adb_command.insert(1, '-s')
178+
adb_command.insert(2, device_id)
179+
subprocess.run(adb_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
180+
181+
def start_profiling(package_name, device_id, profiling_duration, ndk_path):
182+
import threading
183+
184+
print("Starting profiling with simpleperf...")
185+
app_profiler_path = os.path.join(ndk_path, 'simpleperf', 'app_profiler.py')
186+
profiling_cmd = [
187+
app_profiler_path,
188+
'-p', package_name,
189+
'-r', "-g --duration 200 -f 1000 --trace-offcpu -e cpu-clock:u"
190+
]
191+
192+
profiling_process = subprocess.Popen(
193+
profiling_cmd,
194+
stdout=subprocess.PIPE,
195+
stderr=subprocess.PIPE,
196+
universal_newlines=True,
197+
bufsize=1
198+
)
199+
200+
201+
def read_output(pipe):
202+
for line in iter(pipe.readline, ''):
203+
print(line, end='')
204+
pipe.close()
205+
206+
threading.Thread(target=read_output, args=(profiling_process.stdout,), daemon=True).start()
207+
threading.Thread(target=read_output, args=(profiling_process.stderr,), daemon=True).start()
208+
209+
return profiling_process
210+
211+
212+
def stop_profiling(profiling_process):
213+
print("Stopping profiling...")
214+
profiling_process.wait()
215+
print("Profiling stopped.")
216+
217+
def test_download_speed(download_url, browser_info, device_id, profiling_enabled=False, ndk_path=None):
218+
start_event = threading.Event()
219+
end_event = threading.Event()
220+
result_dict = {}
221+
222+
if profiling_enabled:
223+
profiling_duration = 200
224+
profiling_process = start_profiling(browser_info['package_name'], device_id, profiling_duration, ndk_path)
225+
if profiling_process is None:
226+
print("Exiting due to profiling start failure.")
227+
return None
228+
229+
clear_adb_logs(device_id)
230+
231+
log_thread = threading.Thread(target=monitor_adb_logs,
232+
args=(start_event, end_event, result_dict, device_id))
233+
log_thread.start()
234+
235+
start_browser_and_download(download_url, browser_info['package_name'], browser_info['activity_name'], device_id)
236+
237+
time.sleep(5)
238+
239+
if not click_download_button(device_id):
240+
print("Failed to initiate download.")
241+
log_thread.join()
242+
if profiling_enabled:
243+
stop_profiling(profiling_process)
244+
return None
245+
246+
print("Waiting for download to start...")
247+
if not start_event.wait(timeout=60):
248+
print("Download did not start within 60 seconds.")
249+
log_thread.join()
250+
if profiling_enabled:
251+
stop_profiling(profiling_process)
252+
return None
253+
254+
print("Waiting for download to complete...")
255+
if not end_event.wait(timeout=900):
256+
print("Download did not finish within 15 minutes.")
257+
log_thread.join()
258+
if profiling_enabled:
259+
stop_profiling(profiling_process)
260+
return None
261+
262+
start_time = result_dict.get('start_time')
263+
end_time = result_dict.get('end_time')
264+
265+
close_browser(browser_info['package_name'], device_id)
266+
267+
if profiling_enabled:
268+
stop_profiling(profiling_process)
269+
270+
if start_time and end_time:
271+
duration = calculate_download_duration(start_time, end_time)
272+
print(f"Download completed in {duration} seconds.")
273+
else:
274+
print("Could not determine download times.")
275+
duration = None
276+
277+
log_thread.join()
278+
return duration
279+
280+
def main():
281+
#download_url = 'https://link.testfile.org/300MB
282+
# download_url = 'https://link.testfile.org/500MB'
283+
# download_url = 'https://testfile.org/1.3GBiconpng'
284+
# download_url = 'https://testfile.org/file-kali-3.9GB-2'
285+
download_url = 'https://testfile.org/files-5GB-zip'
286+
287+
browsers = {
288+
'1': {
289+
'name': 'Firefox',
290+
'package_name': 'org.mozilla.firefox',
291+
'activity_name': 'org.mozilla.gecko.BrowserApp',
292+
},
293+
'2': {
294+
'name': 'Chrome',
295+
'package_name': 'com.android.chrome',
296+
'activity_name': 'com.google.android.apps.chrome.Main',
297+
}
298+
}
299+
300+
devices = get_connected_devices()
301+
if not devices:
302+
print("No devices connected. Please connect an Android device and try again.")
303+
exit(1)
304+
elif len(devices) == 1:
305+
device_id = devices[0]
306+
print(f"Using device: {device_id}")
307+
308+
profiling_enabled = input("Do you want to enable profiling during the download? (yes/no): ").lower() == 'yes'
309+
ndk_path = None
310+
if profiling_enabled:
311+
ndk_path = input("Please enter the path to your android-ndk directory within mozbuild (e.g., ~/.mozbuild/android-ndk-r27b): ")
312+
while not os.path.isdir(ndk_path):
313+
print("Invalid path. Please try again.")
314+
ndk_path = input("Please enter the path to your android-ndk directory within mozbuild (e.g., ~/.mozbuild/android-ndk-r27b): ")
315+
else:
316+
print("Multiple devices connected:")
317+
for idx, device in enumerate(devices):
318+
print(f"{idx + 1}. {device}")
319+
while True:
320+
try:
321+
choice = int(input("Select the device number to use: "))
322+
if 1 <= choice <= len(devices):
323+
device_id = devices[choice - 1]
324+
print(f"Using device: {device_id}")
325+
break
326+
else:
327+
print(f"Please enter a number between 1 and {len(devices)}.")
328+
except ValueError:
329+
print("Invalid input. Please enter a number.")
330+
331+
profiling_enabled = False
332+
ndk_path = None
333+
print("Profiling is disabled when multiple devices are connected.")
334+
335+
compare_speeds = input("Do you want to compare download speeds between two browsers? (yes/no): ").lower() == 'yes'
336+
337+
durations = {}
338+
339+
if compare_speeds:
340+
for key in ['1', '2']:
341+
browser_info = browsers[key]
342+
print(f"\nTesting download speed with {browser_info['name']} on device {device_id}...")
343+
duration = test_download_speed(download_url, browser_info, device_id, profiling_enabled, ndk_path)
344+
if duration is not None:
345+
print(f"Download with {browser_info['name']} completed in {duration} seconds.")
346+
durations[browser_info['name']] = duration
347+
else:
348+
print(f"Download test with {browser_info['name']} failed.")
349+
else:
350+
print("Select a browser to test:")
351+
for key, browser in browsers.items():
352+
print(f"{key}. {browser['name']}")
353+
browser_choice = input("Enter the number of the browser: ")
354+
while browser_choice not in browsers:
355+
print("Invalid choice. Please try again.")
356+
browser_choice = input("Enter the number of the browser: ")
357+
browser_info = browsers[browser_choice]
358+
print(f"\nTesting download speed with {browser_info['name']} on device {device_id}...")
359+
duration = test_download_speed(download_url, browser_info, device_id, profiling_enabled, ndk_path)
360+
if duration is not None:
361+
print(f"Download completed in {duration} seconds.")
362+
durations[browser_info['name']] = duration
363+
else:
364+
print("\nDownload test failed.")
365+
366+
if compare_speeds and len(durations) == 2:
367+
print("\nDownload speed comparison:")
368+
for browser_name, duration in durations.items():
369+
print(f"{browser_name}: {duration} seconds")
370+
diff = abs(durations['Firefox'] - durations['Chrome'])
371+
if durations['Firefox'] < durations['Chrome']:
372+
faster_browser = 'Firefox'
373+
elif durations['Chrome'] < durations['Firefox']:
374+
faster_browser = 'Chrome'
375+
else:
376+
faster_browser = None
377+
print("\nBoth browsers had the same download duration.")
378+
if faster_browser:
379+
print(f"\n{faster_browser} was faster by {diff} seconds.")
380+
elif len(durations) == 1:
381+
browser_name = list(durations.keys())[0]
382+
print(f"\nDownload with {browser_name} completed in {durations[browser_name]} seconds.")
383+
384+
if profiling_enabled:
385+
print("\nTo view the profiling data, run the following command:")
386+
print("samply import perf.data --breakpad-symbol-server https://symbols.mozilla.org/")
387+
388+
if __name__ == "__main__":
389+
main()

0 commit comments

Comments
 (0)