edq.testing.serverrunner
1import argparse 2import atexit 3import logging 4import signal 5import subprocess 6import time 7import typing 8 9import edq.net.settings 10import edq.util.dirent 11 12DEFAULT_SERVER_STARTUP_INITIAL_WAIT_SECS: float = 0.2 13DEFAULT_STARTUP_WAIT_SECS: float = 10.0 14SERVER_STOP_WAIT_SECS: float = 5.00 15 16DEFAULT_IDENTIFY_MAX_ATTEMPTS: int = 100 17DEFAULT_IDENTIFY_WAIT_SECS: float = 0.25 18 19_logger = logging.getLogger(__name__) 20 21class ServerRunner(): 22 """ 23 A class for running an external HTTP server for some sort of larger process (like testing or generating data). 24 """ 25 26 def __init__(self, 27 server: typing.Union[str, None] = None, 28 server_start_command: typing.Union[str, None] = None, 29 server_stop_command: typing.Union[str, None] = None, 30 http_exchanges_out_dir: typing.Union[str, None] = None, 31 server_output_path: typing.Union[str, None] = None, 32 startup_initial_wait_secs: float = DEFAULT_SERVER_STARTUP_INITIAL_WAIT_SECS, 33 startup_wait_secs: typing.Union[float, None] = None, 34 startup_skip_identify: typing.Union[bool, None] = False, 35 identify_max_attempts: int = DEFAULT_IDENTIFY_MAX_ATTEMPTS, 36 identify_wait_secs: float = DEFAULT_IDENTIFY_WAIT_SECS, 37 skip_restart: bool = False, 38 **kwargs: typing.Any) -> None: 39 if (server is None): 40 raise ValueError('No server specified.') 41 42 self.server: str = server 43 """ The server address to point requests to. """ 44 45 if (server_start_command is None): 46 raise ValueError('No command to start the server was specified.') 47 48 self.server_start_command: str = server_start_command 49 """ The server_start_command to run the LMS server. """ 50 51 self.server_stop_command: typing.Union[str, None] = server_stop_command 52 """ An optional command to stop the server. """ 53 54 if (http_exchanges_out_dir is None): 55 http_exchanges_out_dir = edq.util.dirent.get_temp_dir(prefix = 'edq-serverrunner-http-exchanges-', rm = False) 56 57 self.http_exchanges_out_dir: str = http_exchanges_out_dir 58 """ Where to output the HTTP exchanges. """ 59 60 if (server_output_path is None): 61 server_output_path = edq.util.dirent.get_temp_path(prefix = 'edq-serverrunner-server-output-', rm = False) + '.txt' 62 63 self.server_output_path: str = server_output_path 64 """ Where to write server output (stdout and stderr). """ 65 66 self.startup_initial_wait_secs: float = startup_initial_wait_secs 67 """ The duration to wait after giving the initial startup command. """ 68 69 if (startup_wait_secs is None): 70 startup_wait_secs = DEFAULT_STARTUP_WAIT_SECS 71 72 self.startup_wait_secs = startup_wait_secs 73 """ How long to wait after the server start command is run before making requests to the server. """ 74 75 if (startup_skip_identify is None): 76 startup_skip_identify = False 77 78 self.startup_skip_identify: bool = startup_skip_identify 79 """ 80 Whether to skip trying to identify the server after it has been started. 81 This acts as a way to have a variable wait for the server to start. 82 When not used, self.startup_wait_secs is the only way to wait for the server to start. 83 """ 84 85 self.identify_max_attempts: int = identify_max_attempts 86 """ The maximum number of times to try an identity check before starting the server. """ 87 88 self.identify_wait_secs: float = identify_wait_secs 89 """ The number of seconds each identify request will wait for the server to respond. """ 90 91 self.skip_restart: bool = skip_restart 92 """ While set, self.restart() should become a no-op. """ 93 94 self._old_exchanges_out_dir: typing.Union[str, None] = None 95 """ 96 The value of edq.net.settings.get_exchanges_out_dir() when start() is called. 97 The original value may be changed in start(), and will be reset in stop(). 98 """ 99 100 self._process: typing.Union[subprocess.Popen, None] = None 101 """ The server process. """ 102 103 self._server_output_file: typing.Union[typing.IO, None] = None 104 """ The file that server output is written to. """ 105 106 def start(self) -> None: 107 """ Start the server. """ 108 109 if (self._process is not None): 110 return 111 112 # Ensure stop() is called. 113 atexit.register(self.stop) 114 115 # Store and set networking config. 116 117 self._old_exchanges_out_dir = edq.net.settings.get_exchanges_out_dir() 118 edq.net.settings.set_exchanges_out_dir(self.http_exchanges_out_dir) 119 120 # Start the server. 121 122 _logger.info("Writing HTTP exchanges to '%s'.", self.http_exchanges_out_dir) 123 _logger.info("Writing server output to '%s'.", self.server_output_path) 124 _logger.info("Starting the server ('%s') and waiting for it.", self.server) 125 126 self._server_output_file = open(self.server_output_path, 'a', encoding = edq.util.dirent.DEFAULT_ENCODING) # pylint: disable=consider-using-with 127 128 self._start_server() 129 _logger.info("Server is started up.") 130 131 def _start_server(self) -> None: 132 """ Start the server. """ 133 134 if (self._process is not None): 135 return 136 137 self._process = subprocess.Popen(self.server_start_command, # pylint: disable=consider-using-with 138 shell = True, stdout = self._server_output_file, stderr = subprocess.STDOUT) 139 140 status = None 141 try: 142 # Wait for a short period for the process to start. 143 status = self._process.wait(self.startup_initial_wait_secs) 144 except subprocess.TimeoutExpired: 145 # Good, the server is running. 146 pass 147 148 if (status is not None): 149 hint = f"code: '{status}'" 150 if (status == 125): 151 hint = 'server may already be running' 152 153 raise ValueError(f"Server was unable to start successfully ('{hint}').") 154 155 _logger.info("Completed initial server start wait.") 156 157 # Ping the server to check if it has started. 158 if (not self.startup_skip_identify): 159 for _ in range(self.identify_max_attempts): 160 if (self.identify_server()): 161 # The server is running and responding, exit early. 162 return 163 164 time.sleep(self.identify_wait_secs) 165 166 status = None 167 try: 168 # Ensure the server is running cleanly. 169 status = self._process.wait(self.startup_wait_secs) 170 except subprocess.TimeoutExpired: 171 # Good, the server is running. 172 pass 173 174 if (status is not None): 175 raise ValueError(f"Server was unable to start successfully ('code: {status}').") 176 177 def stop(self) -> bool: 178 """ 179 Stop the server. 180 Return true if child classes should perform shutdown behavior. 181 """ 182 183 if (self._process is None): 184 return False 185 186 # Stop the server. 187 _logger.info('Stopping the server.') 188 self._stop_server() 189 190 # Restore networking config. 191 192 edq.net.settings.set_exchanges_out_dir(self._old_exchanges_out_dir) 193 self._old_exchanges_out_dir = None 194 195 if (self._server_output_file is not None): 196 self._server_output_file.close() 197 self._server_output_file = None 198 199 return True 200 201 def restart(self) -> None: 202 """ Restart the server. """ 203 204 if (self.skip_restart): 205 return 206 207 _logger.debug('Restarting the server.') 208 self._stop_server() 209 self._start_server() 210 211 def identify_server(self) -> bool: 212 """ 213 Attempt to identify the target server and return true on a successful attempt. 214 This is used on startup to wait for the server to complete startup. 215 216 Child classes must implement this or set self.startup_skip_identify to true. 217 """ 218 219 raise NotImplementedError('identify_server') 220 221 def _stop_server(self) -> typing.Union[int, None]: 222 """ Stop the server process and return the exit status. """ 223 224 if (self._process is None): 225 return None 226 227 # Mark the process as dead, so it can be restarted (if need be). 228 current_process = self._process 229 self._process = None 230 231 # Check if the process is already dead. 232 status = current_process.poll() 233 if (status is not None): 234 return status 235 236 # If the user provided a special command, try it. 237 if (self.server_stop_command is not None): 238 subprocess.run(self.server_stop_command, 239 shell = True, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL, 240 check = False) 241 242 status = current_process.poll() 243 if (status is not None): 244 return status 245 246 # Try to end the server gracefully. 247 try: 248 current_process.send_signal(signal.SIGINT) 249 current_process.wait(SERVER_STOP_WAIT_SECS) 250 except subprocess.TimeoutExpired: 251 pass 252 253 status = current_process.poll() 254 if (status is not None): 255 return status 256 257 # End the server hard. 258 try: 259 current_process.kill() 260 current_process.wait(SERVER_STOP_WAIT_SECS) 261 except subprocess.TimeoutExpired: 262 pass 263 264 status = current_process.poll() 265 if (status is not None): 266 return status 267 268 return None 269 270def modify_parser(parser: argparse.ArgumentParser) -> None: 271 """ Modify the parser to add arguments for running a server. """ 272 273 parser.add_argument('server_start_command', metavar = 'RUN_SERVER_COMMAND', 274 action = 'store', type = str, 275 help = 'The command to run the LMS server that will be the target of the data generation commands.') 276 277 group = parser.add_argument_group('server control') 278 279 group.add_argument('--server-output-file', dest = 'server_output_path', 280 action = 'store', type = str, default = None, 281 help = 'Where server output will be written. Defaults to a random temp file.') 282 283 group.add_argument('--server-stop-command', dest = 'server_stop_command', 284 action = 'store', type = str, default = None, 285 help = 'An optional command to stop the server. After this the server will be sent a SIGINT and then a SIGKILL.') 286 287 group.add_argument('--startup-skip-identify', dest = 'startup_skip_identify', 288 action = 'store_true', default = False, 289 help = 'If set, startup will skip trying to identify the server as a means of checking that the server is started.') 290 291 group.add_argument('--startup-wait', dest = 'startup_wait_secs', 292 action = 'store', type = float, default = DEFAULT_STARTUP_WAIT_SECS, 293 help = 'The time to wait between starting the server and sending commands (default: %(default)s).')
22class ServerRunner(): 23 """ 24 A class for running an external HTTP server for some sort of larger process (like testing or generating data). 25 """ 26 27 def __init__(self, 28 server: typing.Union[str, None] = None, 29 server_start_command: typing.Union[str, None] = None, 30 server_stop_command: typing.Union[str, None] = None, 31 http_exchanges_out_dir: typing.Union[str, None] = None, 32 server_output_path: typing.Union[str, None] = None, 33 startup_initial_wait_secs: float = DEFAULT_SERVER_STARTUP_INITIAL_WAIT_SECS, 34 startup_wait_secs: typing.Union[float, None] = None, 35 startup_skip_identify: typing.Union[bool, None] = False, 36 identify_max_attempts: int = DEFAULT_IDENTIFY_MAX_ATTEMPTS, 37 identify_wait_secs: float = DEFAULT_IDENTIFY_WAIT_SECS, 38 skip_restart: bool = False, 39 **kwargs: typing.Any) -> None: 40 if (server is None): 41 raise ValueError('No server specified.') 42 43 self.server: str = server 44 """ The server address to point requests to. """ 45 46 if (server_start_command is None): 47 raise ValueError('No command to start the server was specified.') 48 49 self.server_start_command: str = server_start_command 50 """ The server_start_command to run the LMS server. """ 51 52 self.server_stop_command: typing.Union[str, None] = server_stop_command 53 """ An optional command to stop the server. """ 54 55 if (http_exchanges_out_dir is None): 56 http_exchanges_out_dir = edq.util.dirent.get_temp_dir(prefix = 'edq-serverrunner-http-exchanges-', rm = False) 57 58 self.http_exchanges_out_dir: str = http_exchanges_out_dir 59 """ Where to output the HTTP exchanges. """ 60 61 if (server_output_path is None): 62 server_output_path = edq.util.dirent.get_temp_path(prefix = 'edq-serverrunner-server-output-', rm = False) + '.txt' 63 64 self.server_output_path: str = server_output_path 65 """ Where to write server output (stdout and stderr). """ 66 67 self.startup_initial_wait_secs: float = startup_initial_wait_secs 68 """ The duration to wait after giving the initial startup command. """ 69 70 if (startup_wait_secs is None): 71 startup_wait_secs = DEFAULT_STARTUP_WAIT_SECS 72 73 self.startup_wait_secs = startup_wait_secs 74 """ How long to wait after the server start command is run before making requests to the server. """ 75 76 if (startup_skip_identify is None): 77 startup_skip_identify = False 78 79 self.startup_skip_identify: bool = startup_skip_identify 80 """ 81 Whether to skip trying to identify the server after it has been started. 82 This acts as a way to have a variable wait for the server to start. 83 When not used, self.startup_wait_secs is the only way to wait for the server to start. 84 """ 85 86 self.identify_max_attempts: int = identify_max_attempts 87 """ The maximum number of times to try an identity check before starting the server. """ 88 89 self.identify_wait_secs: float = identify_wait_secs 90 """ The number of seconds each identify request will wait for the server to respond. """ 91 92 self.skip_restart: bool = skip_restart 93 """ While set, self.restart() should become a no-op. """ 94 95 self._old_exchanges_out_dir: typing.Union[str, None] = None 96 """ 97 The value of edq.net.settings.get_exchanges_out_dir() when start() is called. 98 The original value may be changed in start(), and will be reset in stop(). 99 """ 100 101 self._process: typing.Union[subprocess.Popen, None] = None 102 """ The server process. """ 103 104 self._server_output_file: typing.Union[typing.IO, None] = None 105 """ The file that server output is written to. """ 106 107 def start(self) -> None: 108 """ Start the server. """ 109 110 if (self._process is not None): 111 return 112 113 # Ensure stop() is called. 114 atexit.register(self.stop) 115 116 # Store and set networking config. 117 118 self._old_exchanges_out_dir = edq.net.settings.get_exchanges_out_dir() 119 edq.net.settings.set_exchanges_out_dir(self.http_exchanges_out_dir) 120 121 # Start the server. 122 123 _logger.info("Writing HTTP exchanges to '%s'.", self.http_exchanges_out_dir) 124 _logger.info("Writing server output to '%s'.", self.server_output_path) 125 _logger.info("Starting the server ('%s') and waiting for it.", self.server) 126 127 self._server_output_file = open(self.server_output_path, 'a', encoding = edq.util.dirent.DEFAULT_ENCODING) # pylint: disable=consider-using-with 128 129 self._start_server() 130 _logger.info("Server is started up.") 131 132 def _start_server(self) -> None: 133 """ Start the server. """ 134 135 if (self._process is not None): 136 return 137 138 self._process = subprocess.Popen(self.server_start_command, # pylint: disable=consider-using-with 139 shell = True, stdout = self._server_output_file, stderr = subprocess.STDOUT) 140 141 status = None 142 try: 143 # Wait for a short period for the process to start. 144 status = self._process.wait(self.startup_initial_wait_secs) 145 except subprocess.TimeoutExpired: 146 # Good, the server is running. 147 pass 148 149 if (status is not None): 150 hint = f"code: '{status}'" 151 if (status == 125): 152 hint = 'server may already be running' 153 154 raise ValueError(f"Server was unable to start successfully ('{hint}').") 155 156 _logger.info("Completed initial server start wait.") 157 158 # Ping the server to check if it has started. 159 if (not self.startup_skip_identify): 160 for _ in range(self.identify_max_attempts): 161 if (self.identify_server()): 162 # The server is running and responding, exit early. 163 return 164 165 time.sleep(self.identify_wait_secs) 166 167 status = None 168 try: 169 # Ensure the server is running cleanly. 170 status = self._process.wait(self.startup_wait_secs) 171 except subprocess.TimeoutExpired: 172 # Good, the server is running. 173 pass 174 175 if (status is not None): 176 raise ValueError(f"Server was unable to start successfully ('code: {status}').") 177 178 def stop(self) -> bool: 179 """ 180 Stop the server. 181 Return true if child classes should perform shutdown behavior. 182 """ 183 184 if (self._process is None): 185 return False 186 187 # Stop the server. 188 _logger.info('Stopping the server.') 189 self._stop_server() 190 191 # Restore networking config. 192 193 edq.net.settings.set_exchanges_out_dir(self._old_exchanges_out_dir) 194 self._old_exchanges_out_dir = None 195 196 if (self._server_output_file is not None): 197 self._server_output_file.close() 198 self._server_output_file = None 199 200 return True 201 202 def restart(self) -> None: 203 """ Restart the server. """ 204 205 if (self.skip_restart): 206 return 207 208 _logger.debug('Restarting the server.') 209 self._stop_server() 210 self._start_server() 211 212 def identify_server(self) -> bool: 213 """ 214 Attempt to identify the target server and return true on a successful attempt. 215 This is used on startup to wait for the server to complete startup. 216 217 Child classes must implement this or set self.startup_skip_identify to true. 218 """ 219 220 raise NotImplementedError('identify_server') 221 222 def _stop_server(self) -> typing.Union[int, None]: 223 """ Stop the server process and return the exit status. """ 224 225 if (self._process is None): 226 return None 227 228 # Mark the process as dead, so it can be restarted (if need be). 229 current_process = self._process 230 self._process = None 231 232 # Check if the process is already dead. 233 status = current_process.poll() 234 if (status is not None): 235 return status 236 237 # If the user provided a special command, try it. 238 if (self.server_stop_command is not None): 239 subprocess.run(self.server_stop_command, 240 shell = True, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL, 241 check = False) 242 243 status = current_process.poll() 244 if (status is not None): 245 return status 246 247 # Try to end the server gracefully. 248 try: 249 current_process.send_signal(signal.SIGINT) 250 current_process.wait(SERVER_STOP_WAIT_SECS) 251 except subprocess.TimeoutExpired: 252 pass 253 254 status = current_process.poll() 255 if (status is not None): 256 return status 257 258 # End the server hard. 259 try: 260 current_process.kill() 261 current_process.wait(SERVER_STOP_WAIT_SECS) 262 except subprocess.TimeoutExpired: 263 pass 264 265 status = current_process.poll() 266 if (status is not None): 267 return status 268 269 return None
A class for running an external HTTP server for some sort of larger process (like testing or generating data).
27 def __init__(self, 28 server: typing.Union[str, None] = None, 29 server_start_command: typing.Union[str, None] = None, 30 server_stop_command: typing.Union[str, None] = None, 31 http_exchanges_out_dir: typing.Union[str, None] = None, 32 server_output_path: typing.Union[str, None] = None, 33 startup_initial_wait_secs: float = DEFAULT_SERVER_STARTUP_INITIAL_WAIT_SECS, 34 startup_wait_secs: typing.Union[float, None] = None, 35 startup_skip_identify: typing.Union[bool, None] = False, 36 identify_max_attempts: int = DEFAULT_IDENTIFY_MAX_ATTEMPTS, 37 identify_wait_secs: float = DEFAULT_IDENTIFY_WAIT_SECS, 38 skip_restart: bool = False, 39 **kwargs: typing.Any) -> None: 40 if (server is None): 41 raise ValueError('No server specified.') 42 43 self.server: str = server 44 """ The server address to point requests to. """ 45 46 if (server_start_command is None): 47 raise ValueError('No command to start the server was specified.') 48 49 self.server_start_command: str = server_start_command 50 """ The server_start_command to run the LMS server. """ 51 52 self.server_stop_command: typing.Union[str, None] = server_stop_command 53 """ An optional command to stop the server. """ 54 55 if (http_exchanges_out_dir is None): 56 http_exchanges_out_dir = edq.util.dirent.get_temp_dir(prefix = 'edq-serverrunner-http-exchanges-', rm = False) 57 58 self.http_exchanges_out_dir: str = http_exchanges_out_dir 59 """ Where to output the HTTP exchanges. """ 60 61 if (server_output_path is None): 62 server_output_path = edq.util.dirent.get_temp_path(prefix = 'edq-serverrunner-server-output-', rm = False) + '.txt' 63 64 self.server_output_path: str = server_output_path 65 """ Where to write server output (stdout and stderr). """ 66 67 self.startup_initial_wait_secs: float = startup_initial_wait_secs 68 """ The duration to wait after giving the initial startup command. """ 69 70 if (startup_wait_secs is None): 71 startup_wait_secs = DEFAULT_STARTUP_WAIT_SECS 72 73 self.startup_wait_secs = startup_wait_secs 74 """ How long to wait after the server start command is run before making requests to the server. """ 75 76 if (startup_skip_identify is None): 77 startup_skip_identify = False 78 79 self.startup_skip_identify: bool = startup_skip_identify 80 """ 81 Whether to skip trying to identify the server after it has been started. 82 This acts as a way to have a variable wait for the server to start. 83 When not used, self.startup_wait_secs is the only way to wait for the server to start. 84 """ 85 86 self.identify_max_attempts: int = identify_max_attempts 87 """ The maximum number of times to try an identity check before starting the server. """ 88 89 self.identify_wait_secs: float = identify_wait_secs 90 """ The number of seconds each identify request will wait for the server to respond. """ 91 92 self.skip_restart: bool = skip_restart 93 """ While set, self.restart() should become a no-op. """ 94 95 self._old_exchanges_out_dir: typing.Union[str, None] = None 96 """ 97 The value of edq.net.settings.get_exchanges_out_dir() when start() is called. 98 The original value may be changed in start(), and will be reset in stop(). 99 """ 100 101 self._process: typing.Union[subprocess.Popen, None] = None 102 """ The server process. """ 103 104 self._server_output_file: typing.Union[typing.IO, None] = None 105 """ The file that server output is written to. """
How long to wait after the server start command is run before making requests to the server.
Whether to skip trying to identify the server after it has been started. This acts as a way to have a variable wait for the server to start. When not used, self.startup_wait_secs is the only way to wait for the server to start.
The maximum number of times to try an identity check before starting the server.
The number of seconds each identify request will wait for the server to respond.
107 def start(self) -> None: 108 """ Start the server. """ 109 110 if (self._process is not None): 111 return 112 113 # Ensure stop() is called. 114 atexit.register(self.stop) 115 116 # Store and set networking config. 117 118 self._old_exchanges_out_dir = edq.net.settings.get_exchanges_out_dir() 119 edq.net.settings.set_exchanges_out_dir(self.http_exchanges_out_dir) 120 121 # Start the server. 122 123 _logger.info("Writing HTTP exchanges to '%s'.", self.http_exchanges_out_dir) 124 _logger.info("Writing server output to '%s'.", self.server_output_path) 125 _logger.info("Starting the server ('%s') and waiting for it.", self.server) 126 127 self._server_output_file = open(self.server_output_path, 'a', encoding = edq.util.dirent.DEFAULT_ENCODING) # pylint: disable=consider-using-with 128 129 self._start_server() 130 _logger.info("Server is started up.")
Start the server.
178 def stop(self) -> bool: 179 """ 180 Stop the server. 181 Return true if child classes should perform shutdown behavior. 182 """ 183 184 if (self._process is None): 185 return False 186 187 # Stop the server. 188 _logger.info('Stopping the server.') 189 self._stop_server() 190 191 # Restore networking config. 192 193 edq.net.settings.set_exchanges_out_dir(self._old_exchanges_out_dir) 194 self._old_exchanges_out_dir = None 195 196 if (self._server_output_file is not None): 197 self._server_output_file.close() 198 self._server_output_file = None 199 200 return True
Stop the server. Return true if child classes should perform shutdown behavior.
202 def restart(self) -> None: 203 """ Restart the server. """ 204 205 if (self.skip_restart): 206 return 207 208 _logger.debug('Restarting the server.') 209 self._stop_server() 210 self._start_server()
Restart the server.
212 def identify_server(self) -> bool: 213 """ 214 Attempt to identify the target server and return true on a successful attempt. 215 This is used on startup to wait for the server to complete startup. 216 217 Child classes must implement this or set self.startup_skip_identify to true. 218 """ 219 220 raise NotImplementedError('identify_server')
Attempt to identify the target server and return true on a successful attempt. This is used on startup to wait for the server to complete startup.
Child classes must implement this or set self.startup_skip_identify to true.
271def modify_parser(parser: argparse.ArgumentParser) -> None: 272 """ Modify the parser to add arguments for running a server. """ 273 274 parser.add_argument('server_start_command', metavar = 'RUN_SERVER_COMMAND', 275 action = 'store', type = str, 276 help = 'The command to run the LMS server that will be the target of the data generation commands.') 277 278 group = parser.add_argument_group('server control') 279 280 group.add_argument('--server-output-file', dest = 'server_output_path', 281 action = 'store', type = str, default = None, 282 help = 'Where server output will be written. Defaults to a random temp file.') 283 284 group.add_argument('--server-stop-command', dest = 'server_stop_command', 285 action = 'store', type = str, default = None, 286 help = 'An optional command to stop the server. After this the server will be sent a SIGINT and then a SIGKILL.') 287 288 group.add_argument('--startup-skip-identify', dest = 'startup_skip_identify', 289 action = 'store_true', default = False, 290 help = 'If set, startup will skip trying to identify the server as a means of checking that the server is started.') 291 292 group.add_argument('--startup-wait', dest = 'startup_wait_secs', 293 action = 'store', type = float, default = DEFAULT_STARTUP_WAIT_SECS, 294 help = 'The time to wait between starting the server and sending commands (default: %(default)s).')
Modify the parser to add arguments for running a server.