Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
S2-group
GitHub Repository: S2-group/android-runner
Path: blob/master/tests/unit/test_plugins.py
908 views
1
import copy
2
import csv
3
import os.path as op
4
5
import pytest
6
from mock import Mock, call, patch, mock_open
7
from lxml.etree import ElementTree
8
import paths
9
import subprocess
10
import datetime
11
from AndroidRunner.Plugins.android.Android import Android
12
from AndroidRunner.Plugins.Profiler import Profiler
13
from AndroidRunner.Plugins.Profiler import ProfilerException
14
from AndroidRunner.Plugins.trepn.Trepn import Trepn
15
from AndroidRunner.Plugins.perfetto.Perfetto import Perfetto
16
import AndroidRunner.util as util
17
18
class TestPluginTemplate(object):
19
@pytest.fixture()
20
def profiler_template(self):
21
return Profiler('config', [])
22
23
@pytest.fixture()
24
def mock_device(self):
25
return Mock()
26
27
def test_init(self):
28
Profiler('config', [])
29
30
def test_dependencies(self, profiler_template):
31
with pytest.raises(NotImplementedError):
32
profiler_template.dependencies()
33
34
def test_load(self, profiler_template, mock_device):
35
with pytest.raises(NotImplementedError):
36
profiler_template.load(mock_device)
37
38
def test_start_profiling(self, profiler_template, mock_device):
39
with pytest.raises(NotImplementedError):
40
profiler_template.start_profiling(mock_device)
41
42
def test_stop_profiling(self, profiler_template, mock_device):
43
with pytest.raises(NotImplementedError):
44
profiler_template.stop_profiling(mock_device)
45
46
def test_collect_results(self, profiler_template, mock_device):
47
with pytest.raises(NotImplementedError):
48
profiler_template.collect_results(mock_device)
49
50
def test_unload(self, profiler_template, mock_device):
51
with pytest.raises(NotImplementedError):
52
profiler_template.unload(mock_device)
53
54
def test_set_output(self, profiler_template):
55
with pytest.raises(NotImplementedError):
56
profiler_template.set_output('output/dir')
57
58
def test_aggregate_subject(self, profiler_template):
59
with pytest.raises(NotImplementedError):
60
profiler_template.aggregate_subject()
61
62
def test_aggregate_end(self, profiler_template):
63
with pytest.raises(NotImplementedError):
64
profiler_template.aggregate_end('data/dir', 'output/file.csv')
65
66
class TestAndroidPlugin(object):
67
@pytest.fixture()
68
def mock_device(self):
69
return Mock()
70
71
@pytest.fixture()
72
def fixture_dir(self):
73
return op.join(op.dirname(op.abspath(__file__)), 'fixtures')
74
75
@pytest.fixture()
76
def android_plugin(self):
77
test_config = {'sample_interval': 1000, 'data_points': ['cpu', 'mem']}
78
test_paths = {'path1': 'path/1'}
79
return Android(test_config, test_paths)
80
81
@staticmethod
82
def csv_reader_to_table(filename):
83
result = []
84
with open(filename, mode='r') as csv_file:
85
csv_reader = csv.reader(csv_file)
86
for row in csv_reader:
87
result.append(row)
88
return result
89
90
@staticmethod
91
def get_dataset(filename):
92
with open(filename, mode='r') as csv_file:
93
dataset = set(map(tuple, csv.reader(csv_file)))
94
return dataset
95
96
@patch('AndroidRunner.Plugins.Profiler.__init__')
97
def test_android_plugin_succes(self, super_init):
98
test_config = {'sample_interval': 1000, 'data_points': ['cpu', 'mem']}
99
test_paths = {'path1': 'path/1'}
100
ap = Android(test_config, test_paths)
101
102
super_init.assert_called_once_with(test_config, test_paths)
103
assert ap.output_dir == ''
104
assert ap.paths == test_paths
105
assert ap.profile is False
106
assert ap.interval == 1
107
assert ap.data_points == ['cpu', 'mem']
108
assert ap.data == [['datetime', 'cpu', 'mem']]
109
110
@patch('logging.Logger.warning')
111
def test_android_plugin_invalid_datapoints(self, logger_warning):
112
test_config = {'sample_interval': 1000, 'data_points': ['cpu', 'mem', 'invalid']}
113
test_paths = {'path1': 'path/1'}
114
ap = Android(test_config, test_paths)
115
116
assert ap.output_dir == ''
117
assert ap.paths == test_paths
118
assert ap.profile is False
119
assert ap.interval == 1
120
assert ap.data_points == ['cpu', 'mem']
121
assert ap.data == [['datetime', 'cpu', 'mem']]
122
logger_warning.assert_called_once_with("Invalid data points in config: ['invalid']")
123
124
def test_android_plugin_default_interval(self):
125
test_config = {'data_points': ['cpu', 'mem', 'invalid']}
126
test_paths = {'path1': 'path/1'}
127
ap = Android(test_config, test_paths)
128
129
assert ap.output_dir == ''
130
assert ap.paths == test_paths
131
assert ap.profile is False
132
assert ap.interval == 0
133
assert ap.data_points == ['cpu', 'mem']
134
assert ap.data == [['datetime', 'cpu', 'mem']]
135
136
def test_get_cpu_usage(self, android_plugin, mock_device):
137
mock_device.shell.return_value = '30% TOTAL: 21% user + 6.7% kernel + 1.2% iowait + 0.7% irq + 0.5% softirq'
138
cpu_usage = android_plugin.get_cpu_usage(mock_device)
139
140
assert cpu_usage == '30'
141
mock_device.shell.assert_called_once_with('dumpsys cpuinfo | grep TOTAL')
142
143
def test_get_cpu_usage_minus_in(self, android_plugin, mock_device):
144
mock_device.shell.return_value = '30.-6% TOTAL: 21% user + 6.7% kernel + 1.2% iowait + 0.7% irq + 0.5% softirq'
145
cpu_usage = android_plugin.get_cpu_usage(mock_device)
146
147
assert cpu_usage == '30.6'
148
mock_device.shell.assert_called_once_with('dumpsys cpuinfo | grep TOTAL')
149
150
def test_get_mem_usage_no_app(self, android_plugin, mock_device):
151
mock_device.shell.return_value = 'Used RAM: 1016104 kB (819528 used pss + 196576 kernel)'
152
153
mem_usage = android_plugin.get_mem_usage(mock_device, None)
154
155
assert mem_usage == "1016104"
156
mock_device.shell.assert_called_once_with('dumpsys meminfo | grep Used')
157
158
def test_get_mem_usage_app_found(self, android_plugin, mock_device):
159
mock_device.shell.return_value = ' TOTAL 20411 7516 10228 980 36740 28499 8240 ' \
160
'TOTAL: 20411 TOTAL SWAP (KB): 980'
161
162
mem_usage = android_plugin.get_mem_usage(mock_device, 'com.google.android.calendar')
163
164
assert mem_usage == "20411"
165
mock_device.shell.assert_called_once_with('dumpsys meminfo com.google.android.calendar | grep TOTAL')
166
167
def test_get_mem_usage_app_not_found(self, android_plugin, mock_device):
168
mock_device.shell.side_effect = ['', 'No process found for: fake.app']
169
170
with pytest.raises(Exception) as exception:
171
android_plugin.get_mem_usage(mock_device, 'fake.app')
172
173
assert str(exception.value) == 'Android Profiler: No process found for: fake.app'
174
mock_device.shell.mock_calls[0]('dumpsys meminfo fake.app | grep TOTAL')
175
mock_device.shell.mock_calls[1]('dumpsys meminfo fake.app')
176
177
@patch('AndroidRunner.Plugins.android.Android.Android.get_data')
178
def test_start_profiling_with_app(self, get_data_mock, android_plugin, mock_device):
179
kwargs = {'arg1': 1, 'app': 'test.app'}
180
android_plugin.start_profiling(mock_device, **kwargs)
181
182
assert android_plugin.profile is True
183
get_data_mock.assert_called_once_with(mock_device, 'test.app')
184
185
@patch('AndroidRunner.Plugins.android.Android.Android.get_data')
186
def test_start_profiling_without_app(self, get_data_mock, android_plugin, mock_device):
187
kwargs = {'arg1': 1}
188
android_plugin.start_profiling(mock_device, **kwargs)
189
190
assert android_plugin.profile is True
191
get_data_mock.assert_called_once_with(mock_device, None)
192
193
@patch('timeit.default_timer')
194
@patch('threading.Timer')
195
@patch('AndroidRunner.Plugins.android.Android.Android.get_cpu_usage')
196
@patch('AndroidRunner.Plugins.android.Android.Android.get_mem_usage')
197
def test_get_data_all_points(self, get_mem_usage_mock, get_cpu_usage_mock, timer_mock, timeit_mock,
198
android_plugin, mock_device):
199
timeit_mock.side_effect = [100, 200]
200
mock_device.shell.return_value = 'device_time'
201
get_mem_usage_mock.return_value = "mem_usage"
202
get_cpu_usage_mock.return_value = "cpu_usage"
203
mock_timer_result = Mock()
204
timer_mock.return_value = mock_timer_result
205
android_plugin.profile = True
206
android_plugin.interval = 200
207
android_plugin.get_data(mock_device, 'app')
208
209
assert android_plugin.data[1] == ['device_time', 'cpu_usage', 'mem_usage']
210
timer_mock.assert_called_once_with(100, android_plugin.get_data, args=(mock_device, 'app'))
211
mock_timer_result.start.assert_called_once()
212
213
def test_get_data_race(self, android_plugin, mock_device):
214
android_plugin.profile = False
215
216
old_data = copy.deepcopy(android_plugin.data)
217
android_plugin.get_data(mock_device, 'app')
218
219
assert android_plugin.data == old_data
220
221
@patch('timeit.default_timer')
222
@patch('threading.Timer')
223
@patch('AndroidRunner.Plugins.android.Android.Android.get_cpu_usage')
224
@patch('AndroidRunner.Plugins.android.Android.Android.get_mem_usage')
225
def test_get_data_only_mem(self, get_mem_usage_mock, get_cpu_usage_mock, timer_mock, timeit_mock,
226
android_plugin, mock_device):
227
timeit_mock.side_effect = [100, 200]
228
mock_device.shell.return_value = 'device_time'
229
get_mem_usage_mock.return_value = "mem_usage"
230
get_cpu_usage_mock.return_value = "cpu_usage"
231
android_plugin.data_points = ['mem']
232
android_plugin.profile = True
233
android_plugin.get_data(mock_device, 'app')
234
235
assert android_plugin.data[1] == ['device_time', 'mem_usage']
236
237
@patch('timeit.default_timer')
238
@patch('threading.Timer')
239
@patch('AndroidRunner.Plugins.android.Android.Android.get_cpu_usage')
240
@patch('AndroidRunner.Plugins.android.Android.Android.get_mem_usage')
241
def test_get_data_only_cpu(self, get_mem_usage_mock, get_cpu_usage_mock, timer_mock, timeit_mock,
242
android_plugin, mock_device):
243
timeit_mock.side_effect = [100, 200]
244
mock_device.shell.return_value = 'device_time'
245
get_mem_usage_mock.return_value = "mem_usage"
246
get_cpu_usage_mock.return_value = "cpu_usage"
247
android_plugin.data_points = ['cpu']
248
android_plugin.profile = True
249
android_plugin.get_data(mock_device, 'app')
250
251
assert android_plugin.data[1] == ['device_time', 'cpu_usage']
252
253
def test_stop_profiling(self, android_plugin, mock_device):
254
android_plugin.profile = True
255
256
android_plugin.stop_profiling(mock_device)
257
258
assert android_plugin.profile is False
259
260
@patch('time.strftime')
261
def test_collect_results(self, time_mock, android_plugin, mock_device, tmpdir, fixture_dir):
262
test_output_dir = str(tmpdir)
263
time_mock.return_value = 'time'
264
mock_device.id = 'device_id'
265
time_mock.return_value = 'experiment_time'
266
android_plugin.data = self.csv_reader_to_table(op.join(fixture_dir, 'test_android_output.csv'))
267
android_plugin.output_dir = test_output_dir
268
269
android_plugin.collect_results(mock_device)
270
271
assert op.isfile(op.join(test_output_dir, '{}_{}.csv'.format('device_id', 'experiment_time')))
272
273
file_content_created = self.get_dataset(
274
op.join(test_output_dir, '{}_{}.csv'.format('device_id', 'experiment_time')))
275
file_content_original = self.get_dataset(op.join(fixture_dir, 'test_android_output.csv'))
276
assert file_content_created == file_content_original
277
278
def test_set_output(self, android_plugin):
279
test_output_dir = "asdfgbfsdgbf/hjbdsfavav"
280
android_plugin.set_output(test_output_dir)
281
282
assert android_plugin.output_dir == test_output_dir
283
284
def test_dependencies(self, android_plugin):
285
assert android_plugin.dependencies() == []
286
287
def test_load(self, android_plugin, mock_device):
288
assert android_plugin.load(mock_device) is None
289
290
def test_unload(self, android_plugin, mock_device):
291
assert android_plugin.unload(mock_device) is None
292
293
@patch('AndroidRunner.util.write_to_file')
294
@patch('AndroidRunner.Plugins.android.Android.Android.aggregate_android_subject')
295
def test_aggregate_subject(self, aggregate_mock, write_to_file_mock, android_plugin):
296
test_output_dir = 'test/output/dir'
297
android_plugin.output_dir = test_output_dir
298
mock_rows = Mock()
299
aggregate_mock.return_value = mock_rows
300
301
android_plugin.aggregate_subject()
302
303
aggregate_mock.assert_called_once_with(test_output_dir)
304
expected_list = list()
305
expected_list.append(mock_rows)
306
write_to_file_mock.assert_called_once_with(op.join(test_output_dir, 'Aggregated.csv'), expected_list)
307
308
@patch('AndroidRunner.util.write_to_file')
309
@patch('AndroidRunner.Plugins.android.Android.Android.aggregate_final')
310
def test_aggregate_end(self, aggregate_mock, write_to_file_mock, android_plugin):
311
test_data_dir = 'test/output/dir'
312
test_output_file = 'test/output/file.csv'
313
mock_rows = Mock()
314
aggregate_mock.return_value = mock_rows
315
316
android_plugin.aggregate_end(test_data_dir, test_output_file)
317
318
aggregate_mock.assert_called_once_with(test_data_dir)
319
write_to_file_mock.assert_called_once_with(test_output_file, mock_rows)
320
321
def test_aggregate_android_subject(self, android_plugin, fixture_dir):
322
test_subject_log_dir = op.join(fixture_dir, 'android_subject_result')
323
324
test_logs_aggregated = android_plugin.aggregate_android_subject(test_subject_log_dir)
325
assert len(test_logs_aggregated) == 2
326
assert test_logs_aggregated['android_cpu'] == 32.94186117467583
327
assert test_logs_aggregated['android_mem'] == 1131976.3141113652
328
329
@patch("AndroidRunner.Plugins.android.Android.Android.aggregate_android_final")
330
def test_aggregate_final_web(self, aggregate_mock, android_plugin, fixture_dir):
331
test_struct_dir_web = op.join(fixture_dir, 'test_dir_struct', 'data_web')
332
aggregate_mock.side_effect = [{'avg': 1}, {'avg': 2}]
333
334
final_aggregated_result = android_plugin.aggregate_final(test_struct_dir_web)
335
336
assert len(final_aggregated_result) == 2
337
assert len(final_aggregated_result[0]) == 4
338
339
@patch("AndroidRunner.Plugins.android.Android.Android.aggregate_android_final")
340
def test_aggregate_final_native(self, aggregate_mock, android_plugin, fixture_dir):
341
test_struct_dir_native = op.join(fixture_dir, 'test_dir_struct', 'data_native')
342
aggregate_mock.side_effect = [{'avg': 1}, {'avg': 2}]
343
344
final_aggregated_result = android_plugin.aggregate_final(test_struct_dir_native)
345
346
assert len(final_aggregated_result) == 2
347
assert len(final_aggregated_result[0]) == 3
348
349
def test_aggregate_android_final(self, android_plugin, fixture_dir):
350
test_log_dir = op.join(fixture_dir, 'aggregate_final', 'android')
351
aggregated_final_rows = android_plugin.aggregate_android_final(test_log_dir)
352
353
assert len(aggregated_final_rows) == 2
354
assert aggregated_final_rows['android_cpu'] == '19.017852474323064'
355
assert aggregated_final_rows['android_mem'] == '1280213.4222222222'
356
357
class TestPerfettoPlugin(object):
358
359
@pytest.fixture()
360
@patch("AndroidRunner.Plugins.Profiler.__init__")
361
@patch("AndroidRunner.util.load_json")
362
def perfetto_plugin(self, load_json_mock, super_mock):
363
super_mock.return_value = None
364
load_json_mock.return_value = {}
365
config = {"config_file" : "/home/user/perfetto_config.pbtx", "config_file_format" : "text"}
366
test_paths = paths.paths_dict()
367
return Perfetto(config, test_paths)
368
369
@pytest.fixture()
370
def mock_device(self):
371
return Mock()
372
373
@patch('AndroidRunner.Plugins.Profiler.__init__')
374
@patch("AndroidRunner.util.load_json")
375
def test_init(self, load_json_mock, super_mock):
376
config_mock = Mock()
377
test_paths = paths.paths_dict()
378
config = {"config_file" : "perfetto_config.pbtx", "config_file_format" : "text"}
379
load_json_mock.return_value = {}
380
perfetto_plugin = Perfetto(config, test_paths)
381
382
super_mock.assert_called_once_with(config, test_paths)
383
assert perfetto_plugin.perfetto_trace_file_device_path == ""
384
assert perfetto_plugin.perfetto_config_file_device_path == ""
385
assert perfetto_plugin.paths == paths.paths_dict()
386
assert perfetto_plugin.perfetto_config_file_local_path == config["config_file"]
387
assert perfetto_plugin.perfetto_config_file_format == config["config_file_format"]
388
assert perfetto_plugin.adb_path == "adb"
389
390
def test_dependencies(self, perfetto_plugin):
391
assert perfetto_plugin.dependencies() == []
392
393
def test_load(self, perfetto_plugin, mock_device, tmpdir):
394
config_file = tmpdir.mkdir("config_files").join("perfetto_config.pbtx")
395
config_file.write("Perfetto config file")
396
perfetto_plugin.perfetto_config_file_local_path = str(config_file)
397
perfetto_plugin.load(mock_device)
398
399
assert perfetto_plugin.perfetto_config_file_device_path == op.join(perfetto_plugin.PERFETTO_CONFIG_DEVICE_PATH, "perfetto_config.pbtx")
400
mock_device.push.assert_called_with(perfetto_plugin.perfetto_config_file_local_path, perfetto_plugin.perfetto_config_file_device_path)
401
402
def test_load_file_not_found(self, perfetto_plugin, mock_device):
403
perfetto_plugin.perfetto_config_file_local_path = "/home/user/no_file.pbtx"
404
405
with pytest.raises(util.ConfigError) as except_result:
406
perfetto_plugin.load(mock_device)
407
408
assert "Config file not found on host. Is /home/user/no_file.pbtx the correct path?" in str(except_result)
409
410
def test_set_output(self, perfetto_plugin, tmpdir):
411
test_output_dir = str(tmpdir)
412
413
perfetto_plugin.set_output(test_output_dir)
414
415
assert perfetto_plugin.output_dir == test_output_dir
416
417
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
418
@patch("AndroidRunner.Plugins.perfetto.Perfetto.Perfetto._datetime_now")
419
def test_start_profiling_text_config(self, datetime_mock, subprocess_mock, perfetto_plugin, mock_device):
420
datetime_mock.return_value = datetime.datetime(2020, 12, 31, 21, 40, 22, 610621)
421
popen_mock = Mock()
422
popen_mock.communicate.return_value = (b"42", b"")
423
mock_device.id = 20
424
subprocess_mock.return_value = popen_mock
425
426
perfetto_plugin.start_profiling(mock_device)
427
428
assert perfetto_plugin.perfetto_trace_file_device_path == op.join(perfetto_plugin.PERFETTO_TRACES_DEVICE_PATH,
429
"2020_12_31T21_40_22_610621.perfetto_trace")
430
assert perfetto_plugin.perfetto_device_pid == "42"
431
subprocess_mock.assert_called_with(["adb", "-s", mock_device.id, "shell", f"cat {perfetto_plugin.perfetto_config_file_device_path} | perfetto --background --txt -c - -o {perfetto_plugin.perfetto_trace_file_device_path}"],
432
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
433
434
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
435
@patch("AndroidRunner.Plugins.perfetto.Perfetto.Perfetto._datetime_now")
436
def test_start_profiling_binary_config(self, datetime_mock, subprocess_mock, perfetto_plugin, mock_device):
437
datetime_mock.return_value = datetime.datetime(2020, 12, 31, 21, 40, 22, 610621)
438
perfetto_plugin.perfetto_config_file_format = "binary"
439
popen_mock = Mock()
440
popen_mock.communicate.return_value = (b"42", b"")
441
mock_device.id = 20
442
subprocess_mock.return_value = popen_mock
443
empty_string = ""
444
445
perfetto_plugin.start_profiling(mock_device)
446
447
assert perfetto_plugin.perfetto_trace_file_device_path == op.join(perfetto_plugin.PERFETTO_TRACES_DEVICE_PATH,
448
"2020_12_31T21_40_22_610621.perfetto_trace")
449
assert perfetto_plugin.perfetto_device_pid == "42"
450
subprocess_mock.assert_called_with(["adb", "-s", mock_device.id, "shell", f"cat {perfetto_plugin.perfetto_config_file_device_path} | perfetto --background {empty_string} -c - -o {perfetto_plugin.perfetto_trace_file_device_path}"],
451
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
452
453
454
455
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
456
def test_start_profiling_error(self, subprocess_mock, perfetto_plugin, mock_device):
457
perfetto_plugin.perfetto_config_file_format = "binary"
458
popen_mock = Mock()
459
popen_mock.communicate.return_value = (b"", b"Error")
460
mock_device.id = 20
461
subprocess_mock.return_value = popen_mock
462
463
with pytest.raises(ProfilerException):
464
perfetto_plugin.start_profiling(mock_device)
465
466
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
467
def test_start_profiling_error_no_pid(self, subprocess_mock, perfetto_plugin, mock_device):
468
perfetto_plugin.perfetto_config_file_format = "binary"
469
popen_mock = Mock()
470
popen_mock.communicate.return_value = (b"", b"")
471
mock_device.id = 20
472
mock_device.shell.return_value = "22"
473
subprocess_mock.return_value = popen_mock
474
475
perfetto_plugin.start_profiling(mock_device)
476
assert perfetto_plugin.perfetto_device_pid == "22"
477
mock_device.shell.assert_called_once_with("ps -A | grep perfetto | awk '{print $2}'")
478
479
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
480
def test_start_profiling_error_no_pid_ProfilerException(self, subprocess_mock, perfetto_plugin, mock_device):
481
perfetto_plugin.perfetto_config_file_format = "binary"
482
popen_mock = Mock()
483
popen_mock.communicate.return_value = (b"", b"")
484
mock_device.id = 20
485
mock_device.shell.return_value = "22 23 24"
486
subprocess_mock.return_value = popen_mock
487
488
with pytest.raises(ProfilerException):
489
perfetto_plugin.start_profiling(mock_device)
490
491
def test_stop_profiling(self, mock_device, perfetto_plugin):
492
perfetto_plugin.perfetto_device_pid = "42"
493
perfetto_plugin.stop_profiling(mock_device)
494
495
mock_device.shell.assert_called_once_with("kill 42")
496
497
@patch("AndroidRunner.Plugins.perfetto.Perfetto.subprocess.Popen")
498
@patch("builtins.open")
499
def test_collect_results(self, open_mock, subprocess_mock, perfetto_plugin, mock_device) :
500
m = mock_open()
501
popen_mock = Mock()
502
popen_mock.communicate.return_value = (b"42", b"")
503
mock_device.id = 20
504
subprocess_mock.return_value = popen_mock
505
perfetto_plugin.perfetto_trace_file_device_path = op.join(perfetto_plugin.PERFETTO_TRACES_DEVICE_PATH, "filename.perfetto_trace")
506
perfetto_plugin.paths["OUTPUT_DIR"] = "/home/user/"
507
filename = "/home/user/filename.perfetto_trace"
508
509
perfetto_plugin.collect_results(mock_device)
510
511
open_mock.assert_called_once_with(filename, "w")
512
mock_device.shell.assert_called_once_with(f"rm -f {perfetto_plugin.perfetto_trace_file_device_path}")
513
514
def test_unload(self, mock_device, perfetto_plugin):
515
perfetto_plugin.perfetto_config_file_device_path = "/sdcard/perfetto/trace.perfetto_trace"
516
perfetto_plugin.unload(mock_device)
517
518
mock_device.shell.assert_called_once_with(f"rm -Rf {perfetto_plugin.perfetto_config_file_device_path}")
519
520
class TestTrepnPlugin(object):
521
522
@pytest.fixture()
523
def fixture_dir(self):
524
return op.join(op.dirname(op.abspath(__file__)), 'fixtures')
525
526
@pytest.fixture()
527
def mock_device(self):
528
return Mock()
529
530
@pytest.fixture()
531
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.build_preferences')
532
@patch('AndroidRunner.Plugins.Profiler.__init__')
533
def trepn_plugin(self, super_mock, build_preferences_mock):
534
super_mock.return_value = None
535
build_preferences_mock.return_value = None
536
config_mock = Mock()
537
test_paths = paths.paths_dict()
538
return Trepn(config_mock, test_paths)
539
540
@staticmethod
541
def csv_reader_to_table(filename):
542
result = []
543
with open(filename, mode='r') as csv_file:
544
csv_reader = csv.reader(csv_file)
545
for row in csv_reader:
546
result.append(row)
547
return result
548
549
@staticmethod
550
def file_content(filename):
551
with open(filename, 'r') as myfile:
552
content_string = myfile.read()
553
return content_string
554
555
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.build_preferences')
556
@patch('AndroidRunner.Plugins.Profiler.__init__')
557
def test_int(self, super_mock, build_preferences_mock):
558
config_mock = Mock()
559
test_paths = paths.paths_dict()
560
trepn_plugin = Trepn(config_mock, test_paths)
561
562
super_mock.assert_called_once_with(config_mock, test_paths)
563
assert trepn_plugin.output_dir == ''
564
assert trepn_plugin.paths == paths.paths_dict()
565
assert trepn_plugin.pref_dir is None
566
assert trepn_plugin.remote_pref_dir == op.join(trepn_plugin.DEVICE_PATH, 'saved_preferences/')
567
build_preferences_mock.assert_called_once_with(config_mock)
568
569
def test_dependencies(self, trepn_plugin):
570
assert trepn_plugin.dependencies() == ['com.quicinc.trepn']
571
572
def test_override_preferences_preference_not_in_params(self, trepn_plugin):
573
test_params = {'NOTpreferences': {'profiling_interval': 300}, 'data_points': ['battery_power', 'mem_usage']}
574
elem_tree = ElementTree()
575
assert trepn_plugin.override_preferences(test_params, elem_tree) == elem_tree
576
577
def test_build_preferences(self, trepn_plugin, tmpdir, fixture_dir):
578
test_params = {'preferences': {'profiling_interval': 300, 'temperature_units': 'Celsius'}, 'data_points': ['battery_power', 'mem_usage']}
579
trepn_plugin.paths['OUTPUT_DIR'] = str(tmpdir)
580
581
trepn_plugin.build_preferences(test_params)
582
583
expected_dir = op.join(trepn_plugin.paths['OUTPUT_DIR'], 'trepn.pref/')
584
expected_pref_file = op.join(expected_dir, 'com.quicinc.trepn_preferences.xml')
585
expected_dp_file = op.join(expected_dir, 'com.quicinc.preferences.saved_data_points.xml')
586
assert trepn_plugin.pref_dir == expected_dir
587
assert op.isdir(expected_dir)
588
assert op.isfile(expected_pref_file)
589
assert op.isfile(expected_dp_file)
590
assert self.file_content(expected_pref_file) == self.file_content(op.join(fixture_dir, 'exp_trepn_pref.xml'))
591
assert self.file_content(expected_dp_file) == self.file_content(op.join(fixture_dir, 'exp_saved_dp.xml'))
592
593
@patch('time.sleep')
594
def test_load(self, sleep_mock, trepn_plugin, mock_device, tmpdir):
595
test_pref_dir = str(tmpdir)
596
trepn_plugin.pref_dir = test_pref_dir
597
mock_manager = Mock()
598
mock_manager.attach_mock(sleep_mock, 'sleep_managed')
599
mock_manager.attach_mock(mock_device, 'device_managed')
600
601
trepn_plugin.load(mock_device)
602
603
expected_calls = [call.device_managed.push(test_pref_dir, trepn_plugin.remote_pref_dir),
604
call.device_managed.launch_package('com.quicinc.trepn'),
605
call.sleep_managed(5),
606
call.device_managed.shell('am broadcast -a com.quicinc.trepn.load_preferences '
607
'-e com.quicinc.trepn.load_preferences_file "%s"'
608
% op.join(trepn_plugin.remote_pref_dir, 'trepn.pref')),
609
call.sleep_managed(1),
610
call.device_managed.force_stop('com.quicinc.trepn'),
611
call.sleep_managed(2),
612
call.device_managed.shell('am startservice com.quicinc.trepn/.TrepnService')]
613
assert mock_manager.mock_calls == expected_calls
614
615
def test_start_profiling(self, trepn_plugin, mock_device):
616
trepn_plugin.start_profiling(mock_device)
617
618
mock_device.shell.assert_called_once_with('am broadcast -a com.quicinc.trepn.start_profiling')
619
620
def test_stop_profiling(self, trepn_plugin, mock_device):
621
trepn_plugin.stop_profiling(mock_device)
622
623
mock_device.shell.assert_called_once_with('am broadcast -a com.quicinc.trepn.stop_profiling')
624
625
@patch('os.path.exists')
626
@patch('AndroidRunner.util.wait_until')
627
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.filter_results')
628
def test_collect_results(self, filter_results_mock, wait_until_mock, os_path_mock, trepn_plugin, mock_device, tmpdir):
629
tmpdir_str = str(tmpdir)
630
trepn_plugin.output_dir = tmpdir_str
631
os_path_mock.return_value = True
632
mock_device.id = '123'
633
mock_device.shell.return_value = 'Trepn_2019.08.21_224812.db'
634
mock_manager = Mock()
635
mock_manager.attach_mock(mock_device, 'device_managed')
636
mock_manager.attach_mock(wait_until_mock, 'wait_until_managed')
637
mock_manager.attach_mock(filter_results_mock, 'filter_managed')
638
639
trepn_plugin.collect_results(mock_device)
640
641
expected_calls = [call.device_managed.shell(r'ls /sdcard/trepn/ | grep "\.db$"'),
642
call.device_managed.shell('am broadcast -a com.quicinc.trepn.export_to_csv '
643
'-e com.quicinc.trepn.export_db_input_file '
644
'"Trepn_2019.08.21_224812.db" '
645
'-e com.quicinc.trepn.export_csv_output_file '
646
'"123_Trepn_2019.08.21_224812.csv"'),
647
call.wait_until_managed(trepn_plugin.file_exists_and_not_empty, 5, 1, mock_device, trepn_plugin.DEVICE_PATH, "123_Trepn_2019.08.21_224812.csv" ),
648
call.device_managed.pull(op.join(trepn_plugin.DEVICE_PATH, '123_Trepn_2019.08.21_224812.csv')
649
, tmpdir_str),
650
call.wait_until_managed(os_path_mock, 5, 1, op.join(trepn_plugin.output_dir, "123_Trepn_2019.08.21_224812.csv")),
651
call.device_managed.shell(
652
'rm %s' % op.join(trepn_plugin.DEVICE_PATH, 'Trepn_2019.08.21_224812.db')),
653
call.device_managed.shell(
654
'rm %s' % op.join(trepn_plugin.DEVICE_PATH, '123_Trepn_2019.08.21_224812.csv')),
655
call.filter_managed(op.join(tmpdir_str, '123_Trepn_2019.08.21_224812.csv'))]
656
assert mock_manager.mock_calls == expected_calls
657
658
def test_file_exists_and_not_empty_file_found(self, mock_device, trepn_plugin):
659
path_ = "/sdcard/trepn/"
660
file_ = "123_Trepn_2019.08.21_224812.csv"
661
662
mock_device.shell.side_effect = [f"Other data {file_} other data", "Not empty file contents"]
663
res = trepn_plugin.file_exists_and_not_empty(mock_device, path_, file_)
664
665
assert res == True
666
667
def test_file_exists_and_not_empty_file_not_found(self, mock_device, trepn_plugin):
668
path_ = "/sdcard/trepn/"
669
file_ = "123_Trepn_2019.08.21_224812.csv"
670
671
mock_device.shell.side_effect = [f"Other data other data", "Not empty file contents"]
672
res = trepn_plugin.file_exists_and_not_empty(mock_device, path_, file_)
673
674
assert res == False
675
676
def test_file_exists_and_not_empty_file_empty(self, mock_device, trepn_plugin):
677
path_ = "/sdcard/trepn/"
678
file_ = "123_Trepn_2019.08.21_224812.csv"
679
680
mock_device.shell.side_effect = [f"Other {file_} data other data", ""]
681
res = trepn_plugin.file_exists_and_not_empty(mock_device, path_, file_)
682
683
assert res == False
684
685
def test_read_csv(self, trepn_plugin, fixture_dir):
686
test_file = op.join(fixture_dir, 'test_trepn_data_to_filter.csv')
687
assert trepn_plugin.read_csv(test_file) == self.csv_reader_to_table(test_file)
688
689
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.write_list_to_file')
690
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.filter_data')
691
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.read_csv')
692
def test_filter_result(self, read_csv_mock, filter_data_mock, write_mock, trepn_plugin, tmpdir, fixture_dir):
693
test_filename = op.join(str(tmpdir), 'test_file.txt')
694
test_data = self.csv_reader_to_table(op.join(fixture_dir, 'test_output_orig_trepn.csv'))
695
read_csv_mock.return_value = test_data
696
filter_data_result = Mock()
697
filter_data_mock.return_value = filter_data_result
698
statistic_to_filter_out = ['332', '328']
699
trepn_plugin.data_points = statistic_to_filter_out
700
701
trepn_plugin.filter_results(test_filename)
702
read_csv_mock.assert_called_once_with(test_filename)
703
write_mock.assert_called_once_with(test_filename, filter_data_result)
704
filter_data_mock.assert_called_once_with(['Battery Power*', 'Memory Usage'], self.csv_reader_to_table(
705
op.join(fixture_dir, 'test_trepn_data_to_filter.csv')))
706
707
def test_write_list_to_file(self, trepn_plugin, tmpdir):
708
test_filename = op.join(str(tmpdir), 'test_file.txt')
709
test_data = [[], [], []]
710
for i in range(0, 40):
711
test_data[0].append('column_%s' % i)
712
test_data[1].append('column_%s' % i)
713
test_data[2].append('column_%s' % i)
714
715
trepn_plugin.write_list_to_file(test_filename, test_data)
716
717
assert op.isfile(test_filename)
718
assert self.csv_reader_to_table(test_filename) == test_data
719
720
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.filter_columns')
721
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.get_wanted_columns')
722
def test_filter_data(self, get_wanted_columns_mock, filter_columns_mock, trepn_plugin):
723
wanted_statistics_mock = Mock()
724
data_mock = Mock()
725
data_mock_list = [data_mock]
726
wanted_columns_mock = Mock()
727
get_wanted_columns_mock.return_value = wanted_columns_mock
728
filtered_data_mock = Mock()
729
filter_columns_mock.return_value = filtered_data_mock
730
731
filtered_data = trepn_plugin.filter_data(wanted_statistics_mock, data_mock_list)
732
733
get_wanted_columns_mock.assert_called_once_with(wanted_statistics_mock, data_mock)
734
filter_columns_mock.assert_called_once_with(wanted_columns_mock, data_mock_list)
735
assert filtered_data == filtered_data_mock
736
737
def test_filter_columns(self, trepn_plugin):
738
wanted_columns = [6, 7, 16, 17, 30, 31]
739
data_columns = [[], []]
740
for i in range(0, 40):
741
data_columns[0].append('column_%s' % i)
742
data_columns[1].append('column_%s' % i)
743
remaining_data = trepn_plugin.filter_columns(wanted_columns, data_columns)
744
assert len(remaining_data[0]) == len(remaining_data[1]) == 6
745
for row in remaining_data:
746
column_count = 0
747
for column in row:
748
assert column == 'column_%s' % wanted_columns[column_count]
749
column_count += 1
750
751
def test_get_wanted_columns(self, trepn_plugin):
752
test_wanted_statistics = ['value_3', 'value_8', 'value_15']
753
test_header_row = []
754
for i in range(0, 30):
755
test_header_row.append('Time [%s]' % i)
756
test_header_row.append('value_%s [tst]' % i)
757
758
result_columns = trepn_plugin.get_wanted_columns(test_wanted_statistics, test_header_row)
759
760
assert result_columns == [6, 7, 16, 17, 30, 31]
761
762
def test_unload(self, trepn_plugin, mock_device):
763
trepn_plugin.unload(mock_device)
764
765
expected_calls = [call.shell('am stopservice com.quicinc.trepn/.TrepnService'),
766
call.shell('rm -r %s' % op.join(trepn_plugin.remote_pref_dir, 'trepn.pref'))]
767
assert mock_device.mock_calls == expected_calls
768
769
def test_set_output(self, trepn_plugin, tmpdir):
770
test_output_dir = str(tmpdir)
771
772
trepn_plugin.set_output(test_output_dir)
773
774
assert trepn_plugin.output_dir == test_output_dir
775
776
@patch('AndroidRunner.util.write_to_file')
777
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.aggregate_trepn_subject')
778
def test_aggregate_subject(self, aggregate_mock, write_to_file_mock, trepn_plugin):
779
test_output_dir = 'test/output/dir'
780
trepn_plugin.output_dir = test_output_dir
781
mock_rows = Mock()
782
aggregate_mock.return_value = mock_rows
783
784
trepn_plugin.aggregate_subject()
785
786
aggregate_mock.assert_called_once_with(test_output_dir)
787
expected_list = list()
788
expected_list.append(mock_rows)
789
write_to_file_mock.assert_called_once_with(op.join(test_output_dir, 'Aggregated.csv'), expected_list)
790
791
@patch('AndroidRunner.util.write_to_file')
792
@patch('AndroidRunner.Plugins.trepn.Trepn.Trepn.aggregate_final')
793
def test_aggregate_end(self, aggregate_mock, write_to_file_mock, trepn_plugin):
794
test_data_dir = 'test/output/dir'
795
test_output_file = 'test/output/file.csv'
796
mock_rows = Mock()
797
aggregate_mock.return_value = mock_rows
798
799
trepn_plugin.aggregate_end(test_data_dir, test_output_file)
800
801
aggregate_mock.assert_called_once_with(test_data_dir)
802
write_to_file_mock.assert_called_once_with(test_output_file, mock_rows)
803
804
def test_aggregate_trepn_subject(self, trepn_plugin, fixture_dir):
805
test_subject_log_dir = op.join(fixture_dir, 'trepn_subject_result')
806
807
test_logs_aggregated = trepn_plugin.aggregate_trepn_subject(test_subject_log_dir)
808
809
assert len(test_logs_aggregated) == 4
810
assert test_logs_aggregated['Battery Power* [uW] (Delta)'] == 1230355.5
811
assert test_logs_aggregated['Battery Power* [uW] (Raw)'] == 2301245.088235294
812
assert test_logs_aggregated['Battery Temperature [1/10 C]'] == 300.0
813
assert test_logs_aggregated['Memory Usage [KB]'] == 2650836.2352941176
814
815
@patch("AndroidRunner.Plugins.trepn.Trepn.Trepn.aggregate_trepn_final")
816
def test_aggregate_final_web(self, aggregate_mock, trepn_plugin, fixture_dir):
817
test_struct_dir_web = op.join(fixture_dir, 'test_dir_struct', 'data_web')
818
aggregate_mock.side_effect = [{'avg': 1}, {'avg': 2}]
819
820
final_aggregated_result = trepn_plugin.aggregate_final(test_struct_dir_web)
821
822
assert len(final_aggregated_result) == 2
823
assert len(final_aggregated_result[0]) == 4
824
825
@patch("AndroidRunner.Plugins.trepn.Trepn.Trepn.aggregate_trepn_final")
826
def test_aggregate_final_native(self, aggregate_mock, trepn_plugin, fixture_dir):
827
test_struct_dir_native = op.join(fixture_dir, 'test_dir_struct', 'data_native')
828
aggregate_mock.side_effect = [{'avg': 1}, {'avg': 2}]
829
830
final_aggregated_result = trepn_plugin.aggregate_final(test_struct_dir_native)
831
832
assert len(final_aggregated_result) == 2
833
assert len(final_aggregated_result[0]) == 3
834
835
def test_aggregate_trepn_final(self, trepn_plugin, fixture_dir):
836
test_log_dir = op.join(fixture_dir, 'aggregate_final', 'trepn')
837
aggregated_final_rows = trepn_plugin.aggregate_trepn_final(test_log_dir)
838
839
assert len(aggregated_final_rows) == 4
840
assert aggregated_final_rows['Battery Power* [uW] (Delta)'] == '1230355.5'
841
assert aggregated_final_rows['Battery Power* [uW] (Raw)'] == '2301245.088235294'
842
assert aggregated_final_rows['Battery Temperature [1/10 C]'] == '300.0'
843
assert aggregated_final_rows['Memory Usage [KB]'] == '2650836.2352941176'
844
845