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 logging 6 7 import pylib.android_commands 8 import pylib.device.device_utils 9 10 11 class 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.ReadFile(self._cmdline_file) 36 if self._orig_line: 37 self._orig_line = self._orig_line[0].strip() 38 39 # Parse out the flags into a list to facilitate adding and removing flags. 40 self._current_flags = self._TokenizeFlags(self._orig_line) 41 42 def Get(self): 43 """Returns list of current flags.""" 44 return self._current_flags 45 46 def Set(self, flags): 47 """Replaces all flags on the current command line with the flags given. 48 49 Args: 50 flags: A list of flags to set, eg. ['--single-process']. 51 """ 52 if flags: 53 assert flags[0] != 'chrome' 54 55 self._current_flags = flags 56 self._UpdateCommandLineFile() 57 58 def AddFlags(self, flags): 59 """Appends flags to the command line if they aren't already there. 60 61 Args: 62 flags: A list of flags to add on, eg. ['--single-process']. 63 """ 64 if flags: 65 assert flags[0] != 'chrome' 66 67 # Avoid appending flags that are already present. 68 for flag in flags: 69 if flag not in self._current_flags: 70 self._current_flags.append(flag) 71 self._UpdateCommandLineFile() 72 73 def RemoveFlags(self, flags): 74 """Removes flags from the command line, if they exist. 75 76 Args: 77 flags: A list of flags to remove, eg. ['--single-process']. Note that we 78 expect a complete match when removing flags; if you want to remove 79 a switch with a value, you must use the exact string used to add 80 it in the first place. 81 """ 82 if flags: 83 assert flags[0] != 'chrome' 84 85 for flag in flags: 86 if flag in self._current_flags: 87 self._current_flags.remove(flag) 88 self._UpdateCommandLineFile() 89 90 def Restore(self): 91 """Restores the flags to their original state.""" 92 self._current_flags = self._TokenizeFlags(self._orig_line) 93 self._UpdateCommandLineFile() 94 95 def _UpdateCommandLineFile(self): 96 """Writes out the command line to the file, or removes it if empty.""" 97 logging.info('Current flags: %s', self._current_flags) 98 # Root is not required to write to /data/local/tmp/. 99 use_root = '/data/local/tmp/' not in self._cmdline_file 100 if self._current_flags: 101 # The first command line argument doesn't matter as we are not actually 102 # launching the chrome executable using this command line. 103 cmd_line = ' '.join(['_'] + self._current_flags) 104 self._device.WriteFile( 105 self._cmdline_file, cmd_line, as_root=use_root) 106 file_contents = self._device.ReadFile( 107 self._cmdline_file, as_root=use_root) 108 assert len(file_contents) == 1 and file_contents[0] == cmd_line, ( 109 'Failed to set the command line file at %s' % self._cmdline_file) 110 else: 111 self._device.RunShellCommand('rm ' + self._cmdline_file, 112 as_root=use_root) 113 assert not self._device.FileExists(self._cmdline_file), ( 114 'Failed to remove the command line file at %s' % self._cmdline_file) 115 116 @staticmethod 117 def _TokenizeFlags(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