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