• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 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.
4
5import logging
6
7import pylib.android_commands
8import pylib.device.device_utils
9
10
11class FlagChanger(object):
12  """Changes the flags Chrome runs with.
13
14  There are two different use cases for this file:
15  * Flags are permanently set by calling Set().
16  * Flags can be temporarily set for a particular set of unit tests.  These
17    tests should call Restore() to revert the flags to their original state
18    once the tests have completed.
19  """
20
21  def __init__(self, device, cmdline_file):
22    """Initializes the FlagChanger and records the original arguments.
23
24    Args:
25      device: A DeviceUtils instance.
26      cmdline_file: Path to the command line file on the device.
27    """
28    # TODO(jbudorick) Remove once telemetry switches over.
29    if isinstance(device, pylib.android_commands.AndroidCommands):
30      device = pylib.device.device_utils.DeviceUtils(device)
31    self._device = device
32    self._cmdline_file = cmdline_file
33
34    # Save the original flags.
35    self._orig_line = self._device.old_interface.GetFileContents(
36        self._cmdline_file)
37    if self._orig_line:
38      self._orig_line = self._orig_line[0].strip()
39
40    # Parse out the flags into a list to facilitate adding and removing flags.
41    self._current_flags = self._TokenizeFlags(self._orig_line)
42
43  def Get(self):
44    """Returns list of current flags."""
45    return self._current_flags
46
47  def Set(self, flags):
48    """Replaces all flags on the current command line with the flags given.
49
50    Args:
51      flags: A list of flags to set, eg. ['--single-process'].
52    """
53    if flags:
54      assert flags[0] != 'chrome'
55
56    self._current_flags = flags
57    self._UpdateCommandLineFile()
58
59  def AddFlags(self, flags):
60    """Appends flags to the command line if they aren't already there.
61
62    Args:
63      flags: A list of flags to add on, eg. ['--single-process'].
64    """
65    if flags:
66      assert flags[0] != 'chrome'
67
68    # Avoid appending flags that are already present.
69    for flag in flags:
70      if flag not in self._current_flags:
71        self._current_flags.append(flag)
72    self._UpdateCommandLineFile()
73
74  def RemoveFlags(self, flags):
75    """Removes flags from the command line, if they exist.
76
77    Args:
78      flags: A list of flags to remove, eg. ['--single-process'].  Note that we
79             expect a complete match when removing flags; if you want to remove
80             a switch with a value, you must use the exact string used to add
81             it in the first place.
82    """
83    if flags:
84      assert flags[0] != 'chrome'
85
86    for flag in flags:
87      if flag in self._current_flags:
88        self._current_flags.remove(flag)
89    self._UpdateCommandLineFile()
90
91  def Restore(self):
92    """Restores the flags to their original state."""
93    self._current_flags = self._TokenizeFlags(self._orig_line)
94    self._UpdateCommandLineFile()
95
96  def _UpdateCommandLineFile(self):
97    """Writes out the command line to the file, or removes it if empty."""
98    logging.info('Current flags: %s', self._current_flags)
99    # Root is not required to write to /data/local/tmp/.
100    use_root = '/data/local/tmp/' not in self._cmdline_file
101    if self._current_flags:
102      # The first command line argument doesn't matter as we are not actually
103      # launching the chrome executable using this command line.
104      cmd_line = ' '.join(['_'] + self._current_flags)
105      if use_root:
106        self._device.old_interface.SetProtectedFileContents(
107            self._cmdline_file, cmd_line)
108        file_contents = self._device.old_interface.GetProtectedFileContents(
109            self._cmdline_file)
110      else:
111        self._device.old_interface.SetFileContents(self._cmdline_file, cmd_line)
112        file_contents = self._device.old_interface.GetFileContents(
113            self._cmdline_file)
114      assert len(file_contents) == 1 and file_contents[0] == cmd_line, (
115          'Failed to set the command line file at %s' % self._cmdline_file)
116    else:
117      self._device.RunShellCommand('rm ' + self._cmdline_file, root=use_root)
118      assert (
119          not self._device.old_interface.FileExistsOnDevice(
120              self._cmdline_file)), (
121          'Failed to remove the command line file at %s' % self._cmdline_file)
122
123  @staticmethod
124  def _TokenizeFlags(line):
125    """Changes the string containing the command line into a list of flags.
126
127    Follows similar logic to CommandLine.java::tokenizeQuotedArguments:
128    * Flags are split using whitespace, unless the whitespace is within a
129      pair of quotation marks.
130    * Unlike the Java version, we keep the quotation marks around switch
131      values since we need them to re-create the file when new flags are
132      appended.
133
134    Args:
135      line: A string containing the entire command line.  The first token is
136            assumed to be the program name.
137    """
138    if not line:
139      return []
140
141    tokenized_flags = []
142    current_flag = ""
143    within_quotations = False
144
145    # Move through the string character by character and build up each flag
146    # along the way.
147    for c in line.strip():
148      if c is '"':
149        if len(current_flag) > 0 and current_flag[-1] == '\\':
150          # Last char was a backslash; pop it, and treat this " as a literal.
151          current_flag = current_flag[0:-1] + '"'
152        else:
153          within_quotations = not within_quotations
154          current_flag += c
155      elif not within_quotations and (c is ' ' or c is '\t'):
156        if current_flag is not "":
157          tokenized_flags.append(current_flag)
158          current_flag = ""
159      else:
160        current_flag += c
161
162    # Tack on the last flag.
163    if not current_flag:
164      if within_quotations:
165        logging.warn('Unterminated quoted argument: ' + line)
166    else:
167      tokenized_flags.append(current_flag)
168
169    # Return everything but the program name.
170    return tokenized_flags[1:]
171