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 constants
6import traceback
7import warnings
8
9
10# Location where chrome reads command line flags from
11CHROME_COMMAND_FILE = constants.TEST_EXECUTABLE_DIR + '/chrome-command-line'
12
13class FlagChanger(object):
14  """Changes the flags Chrome runs with.
15
16  There are two different use cases for this file:
17  * Flags are permanently set by calling Set().
18  * Flags can be temporarily set for a particular set of unit tests.  These
19    tests should call Restore() to revert the flags to their original state
20    once the tests have completed.
21  """
22
23  def __init__(self, android_cmd):
24    self._android_cmd = android_cmd
25
26    # Save the original flags.
27    self._orig_line = self._android_cmd.GetFileContents(CHROME_COMMAND_FILE)
28    if self._orig_line:
29      self._orig_line = self._orig_line[0].strip()
30
31    # Parse out the flags into a list to facilitate adding and removing flags.
32    self._current_flags = self._TokenizeFlags(self._orig_line)
33
34  def Get(self):
35    """Returns list of current flags."""
36    return self._current_flags
37
38  def Set(self, flags):
39    """Replaces all flags on the current command line with the flags given.
40
41    Args:
42      flags: A list of flags to set, eg. ['--single-process'].
43    """
44    if flags:
45      assert flags[0] != 'chrome'
46
47    self._current_flags = flags
48    self._UpdateCommandLineFile()
49
50  def AddFlags(self, flags):
51    """Appends flags to the command line if they aren't already there.
52
53    Args:
54      flags: A list of flags to add on, eg. ['--single-process'].
55    """
56    if flags:
57      assert flags[0] != 'chrome'
58
59    # Avoid appending flags that are already present.
60    for flag in flags:
61      if flag not in self._current_flags:
62        self._current_flags.append(flag)
63    self._UpdateCommandLineFile()
64
65  def RemoveFlags(self, flags):
66    """Removes flags from the command line, if they exist.
67
68    Args:
69      flags: A list of flags to remove, eg. ['--single-process'].  Note that we
70             expect a complete match when removing flags; if you want to remove
71             a switch with a value, you must use the exact string used to add
72             it in the first place.
73    """
74    if flags:
75      assert flags[0] != 'chrome'
76
77    for flag in flags:
78      if flag in self._current_flags:
79        self._current_flags.remove(flag)
80    self._UpdateCommandLineFile()
81
82  def Restore(self):
83    """Restores the flags to their original state."""
84    self._current_flags = self._TokenizeFlags(self._orig_line)
85    self._UpdateCommandLineFile()
86
87  def _UpdateCommandLineFile(self):
88    """Writes out the command line to the file, or removes it if empty."""
89    print "Current flags: ", self._current_flags
90
91    if self._current_flags:
92      self._android_cmd.SetFileContents(CHROME_COMMAND_FILE,
93                                        'chrome ' +
94                                        ' '.join(self._current_flags))
95    else:
96      self._android_cmd.RunShellCommand('rm ' + CHROME_COMMAND_FILE)
97
98  def _TokenizeFlags(self, line):
99    """Changes the string containing the command line into a list of flags.
100
101    Follows similar logic to CommandLine.java::tokenizeQuotedArguments:
102    * Flags are split using whitespace, unless the whitespace is within a
103      pair of quotation marks.
104    * Unlike the Java version, we keep the quotation marks around switch
105      values since we need them to re-create the file when new flags are
106      appended.
107
108    Args:
109      line: A string containing the entire command line.  The first token is
110            assumed to be the program name.
111    """
112    if not line:
113      return []
114
115    tokenized_flags = []
116    current_flag = ""
117    within_quotations = False
118
119    # Move through the string character by character and build up each flag
120    # along the way.
121    for c in line.strip():
122      if c is '"':
123        if len(current_flag) > 0 and current_flag[-1] == '\\':
124          # Last char was a backslash; pop it, and treat this " as a literal.
125          current_flag = current_flag[0:-1] + '"'
126        else:
127          within_quotations = not within_quotations
128          current_flag += c
129      elif not within_quotations and (c is ' ' or c is '\t'):
130        if current_flag is not "":
131          tokenized_flags.append(current_flag)
132          current_flag = ""
133      else:
134        current_flag += c
135
136    # Tack on the last flag.
137    if not current_flag:
138      if within_quotations:
139        warnings.warn("Unterminated quoted string: " + current_flag)
140    else:
141      tokenized_flags.append(current_flag)
142
143    # Return everything but the program name.
144    return tokenized_flags[1:]
145