1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4import os
5import platform
6import stat
7import unittest
8
9from telemetry import decorators
10from telemetry.internal.platform.tracing_agent import chrome_tracing_agent
11from telemetry.internal.platform.tracing_agent import (
12    chrome_tracing_devtools_manager)
13from telemetry.timeline import tracing_config
14from telemetry.core import cros_interface
15from telemetry.testing import options_for_unittests
16
17
18from devil.android import device_utils
19
20
21class FakeTracingControllerBackend(object):
22  def __init__(self):
23    self.is_tracing_running = False
24
25
26class FakePlatformBackend(object):
27  def __init__(self):
28    self.tracing_controller_backend = FakeTracingControllerBackend()
29
30  def GetOSName(self):
31    return ''
32
33class FakeAndroidPlatformBackend(FakePlatformBackend):
34  def __init__(self):
35    super(FakeAndroidPlatformBackend, self).__init__()
36    devices = device_utils.DeviceUtils.HealthyDevices(None)
37    self.device = devices[0]
38
39  def GetOSName(self):
40    return 'android'
41
42class FakeCrOSPlatformBackend(FakePlatformBackend):
43  def __init__(self):
44    super(FakeCrOSPlatformBackend, self).__init__()
45    remote = options_for_unittests.GetCopy().cros_remote
46    remote_ssh_port = options_for_unittests.GetCopy().cros_remote_ssh_port
47    self.cri = cros_interface.CrOSInterface(
48        remote, remote_ssh_port,
49        options_for_unittests.GetCopy().cros_ssh_identity)
50
51  def GetOSName(self):
52    return 'chromeos'
53
54class FakeDesktopPlatformBackend(FakePlatformBackend):
55  def GetOSName(self):
56    system = platform.system()
57    if system == 'Linux':
58      return 'linux'
59    if system == 'Darwin':
60      return 'mac'
61    if system == 'Windows':
62      return 'win'
63
64
65class FakeContextMap(object):
66  def __init__(self, contexts):
67    self.contexts = contexts
68
69
70class FakeDevtoolsClient(object):
71  def __init__(self, remote_port):
72    self.is_alive = True
73    self.is_tracing_running = False
74    self.remote_port = remote_port
75    self.will_raise_exception_in_stop_tracing = False
76    self.collected = False
77
78  def IsAlive(self):
79    return self.is_alive
80
81  def StartChromeTracing(self, trace_options, timeout=10):
82    del trace_options, timeout  # unused
83    self.is_tracing_running = True
84
85  def StopChromeTracing(self):
86    self.is_tracing_running = False
87    if self.will_raise_exception_in_stop_tracing:
88      raise Exception
89
90  def CollectChromeTracingData(self, trace_data_builder, timeout=30):
91    del trace_data_builder  # unused
92    del timeout # unused
93    self.collected = True
94
95  def IsChromeTracingSupported(self):
96    return True
97
98  def GetUpdatedInspectableContexts(self):
99    return FakeContextMap([])
100
101
102class ChromeTracingAgentTest(unittest.TestCase):
103  def setUp(self):
104    self.platform1 = FakePlatformBackend()
105    self.platform2 = FakePlatformBackend()
106    self.platform3 = FakePlatformBackend()
107
108  def StartTracing(self, platform_backend, enable_chrome_trace=True):
109    assert chrome_tracing_agent.ChromeTracingAgent.IsSupported(platform_backend)
110    agent = chrome_tracing_agent.ChromeTracingAgent(platform_backend)
111    config = tracing_config.TracingConfig()
112    config.enable_chrome_trace = enable_chrome_trace
113    config.chrome_trace_config.category_filter.AddIncludedCategory('foo')
114    agent._platform_backend.tracing_controller_backend.is_tracing_running = True
115    agent._test_config = config
116    agent.StartAgentTracing(config, 10)
117    return agent
118
119  def FlushTracing(self, agent):
120    agent.FlushAgentTracing(agent._test_config, 10, None)
121
122  def StopTracing(self, agent):
123    agent._platform_backend.tracing_controller_backend.is_tracing_running = (
124        False)
125    agent.StopAgentTracing()
126    agent.CollectAgentTraceData(None)
127
128  def testRegisterDevtoolsClient(self):
129    chrome_tracing_devtools_manager.RegisterDevToolsClient(
130        FakeDevtoolsClient(1), self.platform1)
131    chrome_tracing_devtools_manager.RegisterDevToolsClient(
132        FakeDevtoolsClient(2), self.platform1)
133    chrome_tracing_devtools_manager.RegisterDevToolsClient(
134        FakeDevtoolsClient(3), self.platform1)
135
136    tracing_agent_of_platform1 = self.StartTracing(self.platform1)
137
138    chrome_tracing_devtools_manager.RegisterDevToolsClient(
139        FakeDevtoolsClient(4), self.platform1)
140    chrome_tracing_devtools_manager.RegisterDevToolsClient(
141        FakeDevtoolsClient(5), self.platform2)
142
143    self.StopTracing(tracing_agent_of_platform1)
144    chrome_tracing_devtools_manager.RegisterDevToolsClient(
145        FakeDevtoolsClient(6), self.platform1)
146
147  def testIsSupportWithoutStartupTracingSupport(self):
148    self.assertFalse(
149        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform1))
150    self.assertFalse(
151        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform2))
152    self.assertFalse(
153        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform3))
154
155    devtool1 = FakeDevtoolsClient(1)
156    devtool2 = FakeDevtoolsClient(2)
157    chrome_tracing_devtools_manager.RegisterDevToolsClient(
158        devtool1, self.platform1)
159    chrome_tracing_devtools_manager.RegisterDevToolsClient(
160        devtool2, self.platform2)
161    devtool2.is_alive = False
162
163    # Chrome tracing is only supported on platform 1 since only platform 1 has
164    # an alive devtool.
165    self.assertTrue(
166        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform1))
167    self.assertFalse(
168        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform2))
169    self.assertFalse(
170        chrome_tracing_agent.ChromeTracingAgent.IsSupported(self.platform3))
171
172  @decorators.Enabled('linux', 'mac', 'win')
173  def testIsSupportOnDesktopPlatform(self):
174    # Chrome tracing is always supported on desktop platforms because of startup
175    # tracing.
176    desktop_platform = FakeDesktopPlatformBackend()
177    self.assertTrue(
178        chrome_tracing_agent.ChromeTracingAgent.IsSupported(desktop_platform))
179
180    devtool = FakeDevtoolsClient(1)
181    chrome_tracing_devtools_manager.RegisterDevToolsClient(
182        devtool, desktop_platform)
183    self.assertTrue(
184        chrome_tracing_agent.ChromeTracingAgent.IsSupported(desktop_platform))
185
186  def testStartAndStopTracing(self):
187    devtool1 = FakeDevtoolsClient(1)
188    devtool2 = FakeDevtoolsClient(2)
189    devtool3 = FakeDevtoolsClient(3)
190    devtool4 = FakeDevtoolsClient(2)
191    # Register devtools 1, 2, 3 on platform1 and devtool 4 on platform 2
192    chrome_tracing_devtools_manager.RegisterDevToolsClient(
193        devtool1, self.platform1)
194    chrome_tracing_devtools_manager.RegisterDevToolsClient(
195        devtool2, self.platform1)
196    chrome_tracing_devtools_manager.RegisterDevToolsClient(
197        devtool3, self.platform1)
198    chrome_tracing_devtools_manager.RegisterDevToolsClient(
199        devtool4, self.platform2)
200    devtool2.is_alive = False
201
202    tracing_agent1 = self.StartTracing(self.platform1)
203    with self.assertRaises(chrome_tracing_agent.ChromeTracingStartedError):
204      self.StartTracing(self.platform1)
205
206    self.assertTrue(devtool1.is_tracing_running)
207    self.assertFalse(devtool2.is_tracing_running)
208    self.assertTrue(devtool3.is_tracing_running)
209    # Devtool 4 shouldn't have tracing started although it has the same remote
210    # port as devtool 2
211    self.assertFalse(devtool4.is_tracing_running)
212
213
214    self.assertFalse(devtool1.collected)
215    self.StopTracing(tracing_agent1)
216    self.assertTrue(devtool1.collected)
217    self.assertFalse(devtool1.is_tracing_running)
218    self.assertFalse(devtool2.is_tracing_running)
219    self.assertFalse(devtool3.is_tracing_running)
220    self.assertFalse(devtool4.is_tracing_running)
221
222    # Test that it should be ok to start & stop tracing on platform1 again.
223    tracing_agent1 = self.StartTracing(self.platform1)
224    self.StopTracing(tracing_agent1)
225
226    tracing_agent2 = self.StartTracing(self.platform2)
227    self.assertTrue(devtool4.is_tracing_running)
228    self.assertFalse(devtool4.collected)
229    self.StopTracing(tracing_agent2)
230    self.assertFalse(devtool4.is_tracing_running)
231    self.assertTrue(devtool4.collected)
232
233  def testFlushTracing(self):
234    devtool1 = FakeDevtoolsClient(1)
235    devtool2 = FakeDevtoolsClient(2)
236    devtool3 = FakeDevtoolsClient(3)
237    devtool4 = FakeDevtoolsClient(2)
238
239    # Register devtools 1, 2, 3 on platform1 and devtool 4 on platform 2.
240    chrome_tracing_devtools_manager.RegisterDevToolsClient(
241        devtool1, self.platform1)
242    chrome_tracing_devtools_manager.RegisterDevToolsClient(
243        devtool2, self.platform1)
244    chrome_tracing_devtools_manager.RegisterDevToolsClient(
245        devtool3, self.platform1)
246    chrome_tracing_devtools_manager.RegisterDevToolsClient(
247        devtool4, self.platform2)
248    devtool2.is_alive = False
249
250    tracing_agent1 = self.StartTracing(self.platform1)
251
252    self.assertTrue(devtool1.is_tracing_running)
253    self.assertFalse(devtool2.is_tracing_running)
254    self.assertTrue(devtool3.is_tracing_running)
255    # Devtool 4 shouldn't have tracing started although it has the same remote
256    # port as devtool 2.
257    self.assertFalse(devtool4.is_tracing_running)
258
259    for _ in xrange(5):
260      self.FlushTracing(tracing_agent1)
261      self.assertTrue(devtool1.is_tracing_running)
262      self.assertFalse(devtool2.is_tracing_running)
263      self.assertTrue(devtool3.is_tracing_running)
264      self.assertFalse(devtool4.is_tracing_running)
265
266    self.StopTracing(tracing_agent1)
267    self.assertFalse(devtool1.is_tracing_running)
268    self.assertFalse(devtool2.is_tracing_running)
269    self.assertFalse(devtool3.is_tracing_running)
270    self.assertFalse(devtool4.is_tracing_running)
271
272    # Test that it is ok to start, flush & stop tracing on platform1 again.
273    tracing_agent1 = self.StartTracing(self.platform1)
274    self.FlushTracing(tracing_agent1)
275    self.StopTracing(tracing_agent1)
276
277    tracing_agent2 = self.StartTracing(self.platform2)
278    self.assertTrue(devtool4.is_tracing_running)
279    self.FlushTracing(tracing_agent2)
280    self.assertTrue(devtool4.is_tracing_running)
281    self.StopTracing(tracing_agent2)
282    self.assertFalse(devtool4.is_tracing_running)
283
284  def testExceptionRaisedInStopTracing(self):
285    devtool1 = FakeDevtoolsClient(1)
286    devtool2 = FakeDevtoolsClient(2)
287    # Register devtools 1, 2 on platform 1
288    chrome_tracing_devtools_manager.RegisterDevToolsClient(
289        devtool1, self.platform1)
290    chrome_tracing_devtools_manager.RegisterDevToolsClient(
291        devtool2, self.platform1)
292    tracing_agent1 = self.StartTracing(self.platform1)
293
294    self.assertTrue(devtool1.is_tracing_running)
295    self.assertTrue(devtool2.is_tracing_running)
296
297    devtool1.will_raise_exception_in_stop_tracing = True
298    with self.assertRaises(chrome_tracing_agent.ChromeTracingStoppedError):
299      self.StopTracing(tracing_agent1)
300    # Tracing is stopped on both devtools clients even if there is exception.
301    self.assertIsNone(tracing_agent1.trace_config)
302    self.assertFalse(devtool1.is_tracing_running)
303    self.assertFalse(devtool2.is_tracing_running)
304
305    devtool1.is_alive = False
306    devtool2.is_alive = False
307    # Register devtools 3 on platform 1 should not raise any exception.
308    devtool3 = FakeDevtoolsClient(3)
309    chrome_tracing_devtools_manager.RegisterDevToolsClient(
310        devtool3, self.platform1)
311
312    # Start & Stop tracing on platform 1 should work just fine.
313    tracing_agent2 = self.StartTracing(self.platform1)
314    self.StopTracing(tracing_agent2)
315
316  @decorators.Enabled('android')
317  def testCreateAndRemoveTraceConfigFileOnAndroid(self):
318    platform_backend = FakeAndroidPlatformBackend()
319    agent = chrome_tracing_agent.ChromeTracingAgent(platform_backend)
320    self.assertIsNone(agent.trace_config_file)
321
322    config = tracing_config.TracingConfig()
323    agent._CreateTraceConfigFile(config)
324    self.assertIsNotNone(agent.trace_config_file)
325    self.assertTrue(platform_backend.device.PathExists(agent.trace_config_file))
326    config_file_str = platform_backend.device.ReadFile(agent.trace_config_file,
327                                                       as_root=True)
328    self.assertEqual(agent._CreateTraceConfigFileString(config),
329                     config_file_str.strip())
330
331    config_file_path = agent.trace_config_file
332    agent._RemoveTraceConfigFile()
333    self.assertFalse(platform_backend.device.PathExists(config_file_path))
334    self.assertIsNone(agent.trace_config_file)
335    # robust to multiple file removal
336    agent._RemoveTraceConfigFile()
337    self.assertFalse(platform_backend.device.PathExists(config_file_path))
338    self.assertIsNone(agent.trace_config_file)
339
340  @decorators.Enabled('chromeos')
341  def testCreateAndRemoveTraceConfigFileOnCrOS(self):
342    platform_backend = FakeCrOSPlatformBackend()
343    cri = platform_backend.cri
344    agent = chrome_tracing_agent.ChromeTracingAgent(platform_backend)
345    self.assertIsNone(agent.trace_config_file)
346
347    config = tracing_config.TracingConfig()
348    agent._CreateTraceConfigFile(config)
349    self.assertIsNotNone(agent.trace_config_file)
350    self.assertTrue(cri.FileExistsOnDevice(agent.trace_config_file))
351    config_file_str = cri.GetFileContents(agent.trace_config_file)
352    self.assertEqual(agent._CreateTraceConfigFileString(config),
353                     config_file_str.strip())
354
355    config_file_path = agent.trace_config_file
356    agent._RemoveTraceConfigFile()
357    self.assertFalse(cri.FileExistsOnDevice(config_file_path))
358    self.assertIsNone(agent.trace_config_file)
359    # robust to multiple file removal
360    agent._RemoveTraceConfigFile()
361    self.assertFalse(cri.FileExistsOnDevice(config_file_path))
362    self.assertIsNone(agent.trace_config_file)
363
364  @decorators.Enabled('linux', 'mac', 'win')
365  def testCreateAndRemoveTraceConfigFileOnDesktop(self):
366    platform_backend = FakeDesktopPlatformBackend()
367    agent = chrome_tracing_agent.ChromeTracingAgent(platform_backend)
368    self.assertIsNone(agent.trace_config_file)
369
370    config = tracing_config.TracingConfig()
371    agent._CreateTraceConfigFile(config)
372    self.assertIsNotNone(agent.trace_config_file)
373    self.assertTrue(os.path.exists(agent.trace_config_file))
374    self.assertTrue(os.stat(agent.trace_config_file).st_mode & stat.S_IROTH)
375    with open(agent.trace_config_file, 'r') as f:
376      config_file_str = f.read()
377      self.assertEqual(agent._CreateTraceConfigFileString(config),
378                       config_file_str.strip())
379
380    config_file_path = agent.trace_config_file
381    agent._RemoveTraceConfigFile()
382    self.assertFalse(os.path.exists(config_file_path))
383    self.assertIsNone(agent.trace_config_file)
384    # robust to multiple file removal
385    agent._RemoveTraceConfigFile()
386    self.assertFalse(os.path.exists(config_file_path))
387    self.assertIsNone(agent.trace_config_file)
388