1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-20 20:09:07 +00:00

#N/A: Add port_already_in_use rule

This commit is contained in:
Vladimir Iakovlev 2016-08-14 06:59:26 +03:00
parent a8c3c2d728
commit 56851e8d31
3 changed files with 141 additions and 0 deletions

View File

@ -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;
* `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`;
* `port_already_in_use` – kills process that bound port;
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
* `python_execute` – appends missing `.py` when executing Python files;
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';

View 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'

View 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)