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