mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	#N/A: Add port_already_in_use rule
				
					
				
			This commit is contained in:
		| @@ -210,6 +210,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: | |||||||
| * `no_such_file` – creates missing directories with `mv` and `cp` commands; | * `no_such_file` – creates missing directories with `mv` and `cp` commands; | ||||||
| * `open` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`; | * `open` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`; | ||||||
| * `pip_unknown_command` – fixes wrong `pip` commands, for example `pip instatl/pip install`; | * `pip_unknown_command` – fixes wrong `pip` commands, for example `pip instatl/pip install`; | ||||||
|  | * `port_already_in_use` – kills process that bound port; | ||||||
| * `python_command` – prepends `python` when you trying to run not executable/without `./` python script; | * `python_command` – prepends `python` when you trying to run not executable/without `./` python script; | ||||||
| * `python_execute` – appends missing `.py` when executing Python files; | * `python_execute` – appends missing `.py` when executing Python files; | ||||||
| * `quotation_marks` – fixes uneven usage of `'` and `"` when containing args'; | * `quotation_marks` – fixes uneven usage of `'` and `"` when containing args'; | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								tests/rules/test_port_already_in_use.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								tests/rules/test_port_already_in_use.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | from io import BytesIO | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  | from thefuck.rules.port_already_in_use import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  | outputs = [ | ||||||
|  |     ''' | ||||||
|  |  | ||||||
|  | DE 70% 1/1 build modulesevents.js:141 | ||||||
|  |       throw er; // Unhandled 'error' event | ||||||
|  |       ^ | ||||||
|  |  | ||||||
|  | Error: listen EADDRINUSE 127.0.0.1:8080 | ||||||
|  |     at Object.exports._errnoException (util.js:873:11) | ||||||
|  |     at exports._exceptionWithHostPort (util.js:896:20) | ||||||
|  |     at Server._listen2 (net.js:1250:14) | ||||||
|  |     at listen (net.js:1286:10) | ||||||
|  |     at net.js:1395:9 | ||||||
|  |     at GetAddrInfoReqWrap.asyncCallback [as callback] (dns.js:64:16) | ||||||
|  |     at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:83:10) | ||||||
|  |  | ||||||
|  |     ''', | ||||||
|  |     ''' | ||||||
|  | [6:40:01 AM] <START> Building Dependency Graph | ||||||
|  | [6:40:01 AM] <START> Crawling File System | ||||||
|  |  ERROR  Packager can't listen on port 8080 | ||||||
|  | Most likely another process is already using this port | ||||||
|  | Run the following command to find out which process: | ||||||
|  |  | ||||||
|  |    lsof -n -i4TCP:8080 | ||||||
|  |  | ||||||
|  | You can either shut down the other process: | ||||||
|  |  | ||||||
|  |    kill -9 <PID> | ||||||
|  |  | ||||||
|  | or run packager on different port. | ||||||
|  |  | ||||||
|  |     ''', | ||||||
|  |     ''' | ||||||
|  | Traceback (most recent call last): | ||||||
|  |   File "/usr/lib/python3.5/runpy.py", line 184, in _run_module_as_main | ||||||
|  |     "__main__", mod_spec) | ||||||
|  |   File "/usr/lib/python3.5/runpy.py", line 85, in _run_code | ||||||
|  |     exec(code, run_globals) | ||||||
|  |   File "/home/nvbn/exp/code_view/server/code_view/main.py", line 14, in <module> | ||||||
|  |     web.run_app(app) | ||||||
|  |   File "/home/nvbn/.virtualenvs/code_view/lib/python3.5/site-packages/aiohttp/web.py", line 310, in run_app | ||||||
|  |     backlog=backlog)) | ||||||
|  |   File "/usr/lib/python3.5/asyncio/base_events.py", line 373, in run_until_complete | ||||||
|  |     return future.result() | ||||||
|  |   File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result | ||||||
|  |     raise self._exception | ||||||
|  |   File "/usr/lib/python3.5/asyncio/tasks.py", line 240, in _step | ||||||
|  |     result = coro.send(None) | ||||||
|  |   File "/usr/lib/python3.5/asyncio/base_events.py", line 953, in create_server | ||||||
|  |     % (sa, err.strerror.lower())) | ||||||
|  | OSError: [Errno 98] error while attempting to bind on address ('0.0.0.0', 8080): address already in use | ||||||
|  | Task was destroyed but it is pending! | ||||||
|  | task: <Task pending coro=<RedisProtocol._reader_coroutine() running at /home/nvbn/.virtualenvs/code_view/lib/python3.5/site-packages/asyncio_redis/protocol.py:921> wait_for=<Future pending cb=[Task._wakeup()]>> | ||||||
|  |     ''' | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | lsof_stdout = b'''COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME | ||||||
|  | node    18233 nvbn   16u  IPv4 557134      0t0  TCP localhost:http-alt (LISTEN) | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(autouse=True) | ||||||
|  | def lsof(mocker): | ||||||
|  |     patch = mocker.patch('thefuck.rules.port_already_in_use.Popen') | ||||||
|  |     patch.return_value.stdout = BytesIO(lsof_stdout) | ||||||
|  |     return patch | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.usefixtures('no_memoize') | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     'command', | ||||||
|  |     [Command('./app', stdout=output) for output in outputs] | ||||||
|  |     + [Command('./app', stderr=output) for output in outputs]) | ||||||
|  | def test_match(command): | ||||||
|  |     assert match(command) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.usefixtures('no_memoize') | ||||||
|  | @pytest.mark.parametrize('command, lsof_output', [ | ||||||
|  |     (Command('./app'), lsof_stdout), | ||||||
|  |     (Command('./app', stdout=outputs[1]), b''), | ||||||
|  |     (Command('./app', stderr=outputs[2]), b'')]) | ||||||
|  | def test_not_match(lsof, command, lsof_output): | ||||||
|  |     lsof.return_value.stdout = BytesIO(lsof_output) | ||||||
|  |  | ||||||
|  |     assert not match(command) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     'command', | ||||||
|  |     [Command('./app', stdout=output) for output in outputs] | ||||||
|  |     + [Command('./app', stderr=output) for output in outputs]) | ||||||
|  | def test_get_new_command(command): | ||||||
|  |     assert get_new_command(command) == 'kill 18233 && ./app' | ||||||
							
								
								
									
										39
									
								
								thefuck/rules/port_already_in_use.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								thefuck/rules/port_already_in_use.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | import re | ||||||
|  | from subprocess import Popen, PIPE | ||||||
|  | from thefuck.utils import memoize | ||||||
|  | from thefuck.shells import shell | ||||||
|  |  | ||||||
|  | patterns = [r"bind on address \('.*', (?P<port>\d+)\)", | ||||||
|  |             r'Unable to bind [^ ]*:(?P<port>\d+)', | ||||||
|  |             r"can't listen on port (?P<port>\d+)", | ||||||
|  |             r'listen EADDRINUSE [^ ]*:(?P<port>\d+)'] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @memoize | ||||||
|  | def _get_pid_by_port(port): | ||||||
|  |     proc = Popen(['lsof', '-i', ':{}'.format(port)], stdout=PIPE) | ||||||
|  |     lines = proc.stdout.read().decode().split('\n') | ||||||
|  |     if len(lines) > 1: | ||||||
|  |         return lines[1].split()[1] | ||||||
|  |     else: | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @memoize | ||||||
|  | def _get_used_port(command): | ||||||
|  |     for pattern in patterns: | ||||||
|  |         matched = (re.search(pattern, command.stderr) | ||||||
|  |                    or re.search(pattern, command.stdout)) | ||||||
|  |         if matched: | ||||||
|  |             return matched.group('port') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def match(command): | ||||||
|  |     port = _get_used_port(command) | ||||||
|  |     return port and _get_pid_by_port(port) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_new_command(command): | ||||||
|  |     port = _get_used_port(command) | ||||||
|  |     pid = _get_pid_by_port(port) | ||||||
|  |     return shell.and_(u'kill {}'.format(pid), command.script) | ||||||
		Reference in New Issue
	
	Block a user