emacs.d/elpa/elpy-20200202.2031/elpy/tests/test_refactor.py
2020-02-03 19:45:34 +01:00

545 lines
20 KiB
Python

import unittest
import tempfile
import shutil
import os
import mock
import sys
from elpy import refactor
from textwrap import dedent
class RefactorTestCase(unittest.TestCase):
def setUp(self):
self.project_root = tempfile.mkdtemp(prefix="test-refactor-root")
self.addCleanup(shutil.rmtree, self.project_root,
ignore_errors=True)
def create_file(self, name, contents=""):
filename = os.path.join(self.project_root, name)
contents = dedent(contents)
offset = contents.find("_|_")
if offset > -1:
contents = contents[:offset] + contents[offset + 3:]
with open(filename, "w") as f:
f.write(contents)
return filename, offset
def assertSourceEqual(self, first, second, msg=None):
"""Fail if the two objects are unequal, ignoring indentation."""
self.assertEqual(dedent(first), dedent(second), msg=msg)
class TestGetRefactorOptions(RefactorTestCase):
def test_should_only_return_importsmodule_if_not_on_symbol(self):
filename, offset = self.create_file("foo.py",
"""\
import foo
_|_""")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset)
self.assertTrue(all(opt['category'] in ('Imports',
'Module')
for opt in options))
filename, offset = self.create_file("foo.py",
"""\
_|_
import foo""")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset)
self.assertTrue(all(opt['category'] in ('Imports',
'Module')
for opt in options))
def test_should_return_all_if_on_symbol(self):
filename, offset = self.create_file("foo.py",
"import _|_foo")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset)
self.assertTrue(all(opt['category'] in ('Imports',
'Method',
'Module',
'Symbol')
for opt in options))
def test_should_return_only_region_if_endoffset(self):
filename, offset = self.create_file("foo.py",
"import foo")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset, 5)
self.assertTrue(all(opt['category'] == 'Region'
for opt in options))
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
def test_should_treat_from_import_special(self):
filename, offset = self.create_file("foo.py",
"""\
import foo
_|_""")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset)
self.assertFalse(any(opt['name'] == "refactor_froms_to_imports"
for opt in options))
filename, offset = self.create_file("foo.py",
"imp_|_ort foo")
ref = refactor.Refactor(self.project_root, filename)
options = ref.get_refactor_options(offset)
self.assertTrue(any(opt['name'] == "refactor_froms_to_imports"
for opt in options))
class TestGetChanges(RefactorTestCase):
def test_should_fail_if_method_is_not_refactoring(self):
filename, offset = self.create_file("foo.py")
ref = refactor.Refactor(self.project_root, filename)
self.assertRaises(ValueError, ref.get_changes, "bad_name")
def test_should_return_method_results(self):
filename, offset = self.create_file("foo.py")
ref = refactor.Refactor(self.project_root, filename)
with mock.patch.object(ref, 'refactor_extract_method') as test:
test.return_value = "Meep!"
self.assertEqual(ref.get_changes("refactor_extract_method",
1, 2),
"Meep!")
test.assert_called_with(1, 2)
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestIsOnSymbol(RefactorTestCase):
def test_should_find_symbol(self):
filename, offset = self.create_file("test.py", "__B_|_AR = 100")
r = refactor.Refactor(self.project_root, filename)
self.assertTrue(r._is_on_symbol(offset))
# Issue #111
def test_should_find_symbol_with_underscores(self):
filename, offset = self.create_file("test.py", "_|___BAR = 100")
r = refactor.Refactor(self.project_root, filename)
self.assertTrue(r._is_on_symbol(offset))
def test_should_not_find_weird_places(self):
filename, offset = self.create_file("test.py", "hello = _|_ 1 + 1")
r = refactor.Refactor(self.project_root, filename)
self.assertFalse(r._is_on_symbol(offset))
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestFromsToImports(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"""\
_|_from datetime import datetime
d = datetime(2013, 4, 7)
""")
ref = refactor.Refactor(self.project_root, filename)
(change,) = ref.get_changes("refactor_froms_to_imports", offset)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], filename)
self.assertSourceEqual(change['contents'],
"""\
import datetime
d = datetime.datetime(2013, 4, 7)
""")
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestOrganizeImports(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"""\
import unittest, base64
import datetime, json
obj = json.dumps(23)
unittest.TestCase()
""")
ref = refactor.Refactor(self.project_root, filename)
(change,) = ref.get_changes("refactor_organize_imports")
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], filename)
self.assertSourceEqual(change['contents'],
"""\
import json
import unittest
obj = json.dumps(23)
unittest.TestCase()
""")
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestModuleToPackage(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"_|_import os\n")
ref = refactor.Refactor(self.project_root, filename)
changes = ref.refactor_module_to_package()
a, b, c = changes
# Not sure why the a change is there. It's a CHANGE that
# changes nothing...
self.assertEqual(a['diff'], '')
self.assertEqual(b['action'], 'create')
self.assertEqual(b['type'], 'directory')
self.assertEqual(b['path'], os.path.join(self.project_root, "foo"))
self.assertEqual(c['action'], 'move')
self.assertEqual(c['type'], 'file')
self.assertEqual(c['source'], os.path.join(self.project_root,
"foo.py"))
self.assertEqual(c['destination'], os.path.join(self.project_root,
"foo/__init__.py"))
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestRenameAtPoint(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"""\
class Foo(object):
def _|_foo(self):
return 5
def bar(self):
return self.foo()
""")
file2, offset2 = self.create_file(
"bar.py",
"""\
import foo
x = foo.Foo()
x.foo()""")
ref = refactor.Refactor(self.project_root, filename)
first, second = ref.refactor_rename_at_point(offset, "frob",
in_hierarchy=False,
docs=False)
if first['file'] == filename:
a, b = first, second
else:
a, b = second, first
self.assertEqual(a['action'], 'change')
self.assertEqual(a['file'], filename)
self.assertSourceEqual(a['contents'],
"""\
class Foo(object):
def frob(self):
return 5
def bar(self):
return self.frob()
""")
self.assertEqual(b['action'], 'change')
self.assertEqual(b['file'], file2)
self.assertSourceEqual(b['contents'],
"""\
import foo
x = foo.Foo()
x.frob()""")
def test_should_refactor_in_hierarchy(self):
filename, offset = self.create_file(
"foo.py",
"""\
class Foo(object):
def _|_foo(self):
return 5
def bar(self):
return self.foo()
class Bar(Foo):
def foo(self):
return 42
class Baz(object):
def foo(self):
return 42
""")
file2, offset2 = self.create_file(
"bar.py",
"""\
import foo
x, y, z = foo.Foo(), foo.Bar(), foo.Baz()
x.foo()
y.foo()
z.foo()""")
ref = refactor.Refactor(self.project_root, filename)
first, second = ref.refactor_rename_at_point(offset, "frob",
in_hierarchy=True,
docs=False)
if first['file'] == filename:
a, b = first, second
else:
a, b = second, first
self.assertEqual(a['action'], 'change')
self.assertEqual(a['file'], filename)
self.assertSourceEqual(a['contents'],
"""\
class Foo(object):
def frob(self):
return 5
def bar(self):
return self.frob()
class Bar(Foo):
def frob(self):
return 42
class Baz(object):
def foo(self):
return 42
""")
self.assertEqual(b['action'], 'change')
self.assertEqual(b['file'], file2)
self.assertSourceEqual(b['contents'],
"""\
import foo
x, y, z = foo.Foo(), foo.Bar(), foo.Baz()
x.frob()
y.frob()
z.foo()""")
def test_should_refactor_in_docstrings(self):
filename, offset = self.create_file(
"foo.py",
"""\
class Foo(object):
"Frobnicate the foo"
def _|_foo(self):
return 5
print("I'm an unrelated foo")
""")
ref = refactor.Refactor(self.project_root, filename)
(change,) = ref.refactor_rename_at_point(offset, "frob",
in_hierarchy=False,
docs=True)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], filename)
self.assertSourceEqual(change['contents'],
"""\
class Foo(object):
"Frobnicate the frob"
def frob(self):
return 5
print("I'm an unrelated foo")
""")
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestRenameCurrentModule(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"_|_import os\n")
file2, offset = self.create_file(
"bar.py",
"""\
_|_import foo
foo.os
""")
dest = os.path.join(self.project_root, "frob.py")
ref = refactor.Refactor(self.project_root, filename)
a, b = ref.refactor_rename_current_module("frob")
self.assertEqual(a['action'], 'change')
self.assertEqual(a['file'], file2)
self.assertEqual(a['contents'],
"import frob\n"
"frob.os\n")
self.assertEqual(b['action'], 'move')
self.assertEqual(b['type'], 'file')
self.assertEqual(b['source'], filename)
self.assertEqual(b['destination'], dest)
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestMoveModule(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"_|_import os\n")
file2, offset = self.create_file(
"bar.py",
"""\
_|_import foo
foo.os
""")
dest = os.path.join(self.project_root, "frob")
os.mkdir(dest)
with open(os.path.join(dest, "__init__.py"), "w") as f:
f.write("")
ref = refactor.Refactor(self.project_root, filename)
a, b = ref.refactor_move_module(dest)
self.assertEqual(a['action'], 'change')
self.assertEqual(a['file'], file2)
self.assertSourceEqual(a['contents'],
"""\
import frob.foo
frob.foo.os
""")
self.assertEqual(b['action'], 'move')
self.assertEqual(b['type'], 'file')
self.assertEqual(b['source'], filename)
self.assertEqual(b['destination'],
os.path.join(dest, "foo.py"))
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestCreateInline(RefactorTestCase):
def setUp(self):
super(TestCreateInline, self).setUp()
self.filename, self.offset = self.create_file(
"foo.py",
"""\
def add(a, b):
return a + b
x = _|_add(2, 3)
y = add(17, 4)
""")
def test_should_refactor_single_occurrenc(self):
ref = refactor.Refactor(self.project_root, self.filename)
(change,) = ref.refactor_create_inline(self.offset, True)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], self.filename)
self.assertSourceEqual(change['contents'],
"""\
def add(a, b):
return a + b
x = 2 + 3
y = add(17, 4)
""")
def test_should_refactor_all_occurrencs(self):
ref = refactor.Refactor(self.project_root, self.filename)
(change,) = ref.refactor_create_inline(self.offset, False)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], self.filename)
self.assertSourceEqual(change['contents'],
"""\
x = 2 + 3
y = 17 + 4
""")
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestExtractMethod(RefactorTestCase):
def setUp(self):
super(TestExtractMethod, self).setUp()
self.filename, self.offset = self.create_file(
"foo.py",
"""\
class Foo(object):
def spaghetti(self, a, b):
_|_x = a + 5
y = b + 23
return y
""")
@unittest.skipIf(sys.version_info >= (3, 5), "Python 3.5 not supported")
def test_should_refactor_local(self):
ref = refactor.Refactor(self.project_root, self.filename)
(change,) = ref.refactor_extract_method(self.offset, 104,
"calc", False)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], self.filename)
expected = """\
class Foo(object):
def spaghetti(self, a, b):
return self.calc(a, b)
def calc(self, a, b):
x = a + 5
y = b + 23
return y
"""
expected2 = expected.replace("return self.calc(a, b)",
"return self.calc(b, a)")
expected2 = expected2.replace("def calc(self, a, b)",
"def calc(self, b, a)")
# This is silly, but it's what we got.
if change['contents'] == dedent(expected2):
self.assertSourceEqual(change['contents'], expected2)
else:
self.assertSourceEqual(change['contents'], expected)
@unittest.skipIf(sys.version_info >= (3, 5), "Python 3.5 not supported")
def test_should_refactor_global(self):
ref = refactor.Refactor(self.project_root, self.filename)
(change,) = ref.refactor_extract_method(self.offset, 104,
"calc", True)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], self.filename)
expected = """\
class Foo(object):
def spaghetti(self, a, b):
return calc(a, b)
def calc(a, b):
x = a + 5
y = b + 23
return y
"""
expected2 = expected.replace("return calc(a, b)",
"return calc(b, a)")
expected2 = expected2.replace("def calc(a, b)",
"def calc(b, a)")
if change['contents'] == dedent(expected2):
self.assertSourceEqual(change['contents'], expected2)
else:
self.assertSourceEqual(change['contents'], expected)
@unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
class TestUseFunction(RefactorTestCase):
def test_should_refactor(self):
filename, offset = self.create_file(
"foo.py",
"""\
def _|_add_and_multiply(a, b, c):
temp = a + b
return temp * c
f = 1 + 2
g = f * 3
""")
ref = refactor.Refactor(self.project_root, filename)
(change,) = ref.refactor_use_function(offset)
self.assertEqual(change['action'], 'change')
self.assertEqual(change['file'], filename)
self.assertSourceEqual(change['contents'],
"""\
def add_and_multiply(a, b, c):
temp = a + b
return temp * c
g = add_and_multiply(1, 2, 3)
""")