Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
20 KiB

5 years ago
  1. import unittest
  2. import tempfile
  3. import shutil
  4. import os
  5. import mock
  6. import sys
  7. from elpy import refactor
  8. from textwrap import dedent
  9. class RefactorTestCase(unittest.TestCase):
  10. def setUp(self):
  11. self.project_root = tempfile.mkdtemp(prefix="test-refactor-root")
  12. self.addCleanup(shutil.rmtree, self.project_root,
  13. ignore_errors=True)
  14. def create_file(self, name, contents=""):
  15. filename = os.path.join(self.project_root, name)
  16. contents = dedent(contents)
  17. offset = contents.find("_|_")
  18. if offset > -1:
  19. contents = contents[:offset] + contents[offset + 3:]
  20. with open(filename, "w") as f:
  21. f.write(contents)
  22. return filename, offset
  23. def assertSourceEqual(self, first, second, msg=None):
  24. """Fail if the two objects are unequal, ignoring indentation."""
  25. self.assertEqual(dedent(first), dedent(second), msg=msg)
  26. class TestGetRefactorOptions(RefactorTestCase):
  27. def test_should_only_return_importsmodule_if_not_on_symbol(self):
  28. filename, offset = self.create_file("foo.py",
  29. """\
  30. import foo
  31. _|_""")
  32. ref = refactor.Refactor(self.project_root, filename)
  33. options = ref.get_refactor_options(offset)
  34. self.assertTrue(all(opt['category'] in ('Imports',
  35. 'Module')
  36. for opt in options))
  37. filename, offset = self.create_file("foo.py",
  38. """\
  39. _|_
  40. import foo""")
  41. ref = refactor.Refactor(self.project_root, filename)
  42. options = ref.get_refactor_options(offset)
  43. self.assertTrue(all(opt['category'] in ('Imports',
  44. 'Module')
  45. for opt in options))
  46. def test_should_return_all_if_on_symbol(self):
  47. filename, offset = self.create_file("foo.py",
  48. "import _|_foo")
  49. ref = refactor.Refactor(self.project_root, filename)
  50. options = ref.get_refactor_options(offset)
  51. self.assertTrue(all(opt['category'] in ('Imports',
  52. 'Method',
  53. 'Module',
  54. 'Symbol')
  55. for opt in options))
  56. def test_should_return_only_region_if_endoffset(self):
  57. filename, offset = self.create_file("foo.py",
  58. "import foo")
  59. ref = refactor.Refactor(self.project_root, filename)
  60. options = ref.get_refactor_options(offset, 5)
  61. self.assertTrue(all(opt['category'] == 'Region'
  62. for opt in options))
  63. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  64. def test_should_treat_from_import_special(self):
  65. filename, offset = self.create_file("foo.py",
  66. """\
  67. import foo
  68. _|_""")
  69. ref = refactor.Refactor(self.project_root, filename)
  70. options = ref.get_refactor_options(offset)
  71. self.assertFalse(any(opt['name'] == "refactor_froms_to_imports"
  72. for opt in options))
  73. filename, offset = self.create_file("foo.py",
  74. "imp_|_ort foo")
  75. ref = refactor.Refactor(self.project_root, filename)
  76. options = ref.get_refactor_options(offset)
  77. self.assertTrue(any(opt['name'] == "refactor_froms_to_imports"
  78. for opt in options))
  79. class TestGetChanges(RefactorTestCase):
  80. def test_should_fail_if_method_is_not_refactoring(self):
  81. filename, offset = self.create_file("foo.py")
  82. ref = refactor.Refactor(self.project_root, filename)
  83. self.assertRaises(ValueError, ref.get_changes, "bad_name")
  84. def test_should_return_method_results(self):
  85. filename, offset = self.create_file("foo.py")
  86. ref = refactor.Refactor(self.project_root, filename)
  87. with mock.patch.object(ref, 'refactor_extract_method') as test:
  88. test.return_value = "Meep!"
  89. self.assertEqual(ref.get_changes("refactor_extract_method",
  90. 1, 2),
  91. "Meep!")
  92. test.assert_called_with(1, 2)
  93. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  94. class TestIsOnSymbol(RefactorTestCase):
  95. def test_should_find_symbol(self):
  96. filename, offset = self.create_file("test.py", "__B_|_AR = 100")
  97. r = refactor.Refactor(self.project_root, filename)
  98. self.assertTrue(r._is_on_symbol(offset))
  99. # Issue #111
  100. def test_should_find_symbol_with_underscores(self):
  101. filename, offset = self.create_file("test.py", "_|___BAR = 100")
  102. r = refactor.Refactor(self.project_root, filename)
  103. self.assertTrue(r._is_on_symbol(offset))
  104. def test_should_not_find_weird_places(self):
  105. filename, offset = self.create_file("test.py", "hello = _|_ 1 + 1")
  106. r = refactor.Refactor(self.project_root, filename)
  107. self.assertFalse(r._is_on_symbol(offset))
  108. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  109. class TestFromsToImports(RefactorTestCase):
  110. def test_should_refactor(self):
  111. filename, offset = self.create_file(
  112. "foo.py",
  113. """\
  114. _|_from datetime import datetime
  115. d = datetime(2013, 4, 7)
  116. """)
  117. ref = refactor.Refactor(self.project_root, filename)
  118. (change,) = ref.get_changes("refactor_froms_to_imports", offset)
  119. self.assertEqual(change['action'], 'change')
  120. self.assertEqual(change['file'], filename)
  121. self.assertSourceEqual(change['contents'],
  122. """\
  123. import datetime
  124. d = datetime.datetime(2013, 4, 7)
  125. """)
  126. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  127. class TestOrganizeImports(RefactorTestCase):
  128. def test_should_refactor(self):
  129. filename, offset = self.create_file(
  130. "foo.py",
  131. """\
  132. import unittest, base64
  133. import datetime, json
  134. obj = json.dumps(23)
  135. unittest.TestCase()
  136. """)
  137. ref = refactor.Refactor(self.project_root, filename)
  138. (change,) = ref.get_changes("refactor_organize_imports")
  139. self.assertEqual(change['action'], 'change')
  140. self.assertEqual(change['file'], filename)
  141. self.assertSourceEqual(change['contents'],
  142. """\
  143. import json
  144. import unittest
  145. obj = json.dumps(23)
  146. unittest.TestCase()
  147. """)
  148. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  149. class TestModuleToPackage(RefactorTestCase):
  150. def test_should_refactor(self):
  151. filename, offset = self.create_file(
  152. "foo.py",
  153. "_|_import os\n")
  154. ref = refactor.Refactor(self.project_root, filename)
  155. changes = ref.refactor_module_to_package()
  156. a, b, c = changes
  157. # Not sure why the a change is there. It's a CHANGE that
  158. # changes nothing...
  159. self.assertEqual(a['diff'], '')
  160. self.assertEqual(b['action'], 'create')
  161. self.assertEqual(b['type'], 'directory')
  162. self.assertEqual(b['path'], os.path.join(self.project_root, "foo"))
  163. self.assertEqual(c['action'], 'move')
  164. self.assertEqual(c['type'], 'file')
  165. self.assertEqual(c['source'], os.path.join(self.project_root,
  166. "foo.py"))
  167. self.assertEqual(c['destination'], os.path.join(self.project_root,
  168. "foo/__init__.py"))
  169. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  170. class TestRenameAtPoint(RefactorTestCase):
  171. def test_should_refactor(self):
  172. filename, offset = self.create_file(
  173. "foo.py",
  174. """\
  175. class Foo(object):
  176. def _|_foo(self):
  177. return 5
  178. def bar(self):
  179. return self.foo()
  180. """)
  181. file2, offset2 = self.create_file(
  182. "bar.py",
  183. """\
  184. import foo
  185. x = foo.Foo()
  186. x.foo()""")
  187. ref = refactor.Refactor(self.project_root, filename)
  188. first, second = ref.refactor_rename_at_point(offset, "frob",
  189. in_hierarchy=False,
  190. docs=False)
  191. if first['file'] == filename:
  192. a, b = first, second
  193. else:
  194. a, b = second, first
  195. self.assertEqual(a['action'], 'change')
  196. self.assertEqual(a['file'], filename)
  197. self.assertSourceEqual(a['contents'],
  198. """\
  199. class Foo(object):
  200. def frob(self):
  201. return 5
  202. def bar(self):
  203. return self.frob()
  204. """)
  205. self.assertEqual(b['action'], 'change')
  206. self.assertEqual(b['file'], file2)
  207. self.assertSourceEqual(b['contents'],
  208. """\
  209. import foo
  210. x = foo.Foo()
  211. x.frob()""")
  212. def test_should_refactor_in_hierarchy(self):
  213. filename, offset = self.create_file(
  214. "foo.py",
  215. """\
  216. class Foo(object):
  217. def _|_foo(self):
  218. return 5
  219. def bar(self):
  220. return self.foo()
  221. class Bar(Foo):
  222. def foo(self):
  223. return 42
  224. class Baz(object):
  225. def foo(self):
  226. return 42
  227. """)
  228. file2, offset2 = self.create_file(
  229. "bar.py",
  230. """\
  231. import foo
  232. x, y, z = foo.Foo(), foo.Bar(), foo.Baz()
  233. x.foo()
  234. y.foo()
  235. z.foo()""")
  236. ref = refactor.Refactor(self.project_root, filename)
  237. first, second = ref.refactor_rename_at_point(offset, "frob",
  238. in_hierarchy=True,
  239. docs=False)
  240. if first['file'] == filename:
  241. a, b = first, second
  242. else:
  243. a, b = second, first
  244. self.assertEqual(a['action'], 'change')
  245. self.assertEqual(a['file'], filename)
  246. self.assertSourceEqual(a['contents'],
  247. """\
  248. class Foo(object):
  249. def frob(self):
  250. return 5
  251. def bar(self):
  252. return self.frob()
  253. class Bar(Foo):
  254. def frob(self):
  255. return 42
  256. class Baz(object):
  257. def foo(self):
  258. return 42
  259. """)
  260. self.assertEqual(b['action'], 'change')
  261. self.assertEqual(b['file'], file2)
  262. self.assertSourceEqual(b['contents'],
  263. """\
  264. import foo
  265. x, y, z = foo.Foo(), foo.Bar(), foo.Baz()
  266. x.frob()
  267. y.frob()
  268. z.foo()""")
  269. def test_should_refactor_in_docstrings(self):
  270. filename, offset = self.create_file(
  271. "foo.py",
  272. """\
  273. class Foo(object):
  274. "Frobnicate the foo"
  275. def _|_foo(self):
  276. return 5
  277. print("I'm an unrelated foo")
  278. """)
  279. ref = refactor.Refactor(self.project_root, filename)
  280. (change,) = ref.refactor_rename_at_point(offset, "frob",
  281. in_hierarchy=False,
  282. docs=True)
  283. self.assertEqual(change['action'], 'change')
  284. self.assertEqual(change['file'], filename)
  285. self.assertSourceEqual(change['contents'],
  286. """\
  287. class Foo(object):
  288. "Frobnicate the frob"
  289. def frob(self):
  290. return 5
  291. print("I'm an unrelated foo")
  292. """)
  293. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  294. class TestRenameCurrentModule(RefactorTestCase):
  295. def test_should_refactor(self):
  296. filename, offset = self.create_file(
  297. "foo.py",
  298. "_|_import os\n")
  299. file2, offset = self.create_file(
  300. "bar.py",
  301. """\
  302. _|_import foo
  303. foo.os
  304. """)
  305. dest = os.path.join(self.project_root, "frob.py")
  306. ref = refactor.Refactor(self.project_root, filename)
  307. a, b = ref.refactor_rename_current_module("frob")
  308. self.assertEqual(a['action'], 'change')
  309. self.assertEqual(a['file'], file2)
  310. self.assertEqual(a['contents'],
  311. "import frob\n"
  312. "frob.os\n")
  313. self.assertEqual(b['action'], 'move')
  314. self.assertEqual(b['type'], 'file')
  315. self.assertEqual(b['source'], filename)
  316. self.assertEqual(b['destination'], dest)
  317. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  318. class TestMoveModule(RefactorTestCase):
  319. def test_should_refactor(self):
  320. filename, offset = self.create_file(
  321. "foo.py",
  322. "_|_import os\n")
  323. file2, offset = self.create_file(
  324. "bar.py",
  325. """\
  326. _|_import foo
  327. foo.os
  328. """)
  329. dest = os.path.join(self.project_root, "frob")
  330. os.mkdir(dest)
  331. with open(os.path.join(dest, "__init__.py"), "w") as f:
  332. f.write("")
  333. ref = refactor.Refactor(self.project_root, filename)
  334. a, b = ref.refactor_move_module(dest)
  335. self.assertEqual(a['action'], 'change')
  336. self.assertEqual(a['file'], file2)
  337. self.assertSourceEqual(a['contents'],
  338. """\
  339. import frob.foo
  340. frob.foo.os
  341. """)
  342. self.assertEqual(b['action'], 'move')
  343. self.assertEqual(b['type'], 'file')
  344. self.assertEqual(b['source'], filename)
  345. self.assertEqual(b['destination'],
  346. os.path.join(dest, "foo.py"))
  347. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  348. class TestCreateInline(RefactorTestCase):
  349. def setUp(self):
  350. super(TestCreateInline, self).setUp()
  351. self.filename, self.offset = self.create_file(
  352. "foo.py",
  353. """\
  354. def add(a, b):
  355. return a + b
  356. x = _|_add(2, 3)
  357. y = add(17, 4)
  358. """)
  359. def test_should_refactor_single_occurrenc(self):
  360. ref = refactor.Refactor(self.project_root, self.filename)
  361. (change,) = ref.refactor_create_inline(self.offset, True)
  362. self.assertEqual(change['action'], 'change')
  363. self.assertEqual(change['file'], self.filename)
  364. self.assertSourceEqual(change['contents'],
  365. """\
  366. def add(a, b):
  367. return a + b
  368. x = 2 + 3
  369. y = add(17, 4)
  370. """)
  371. def test_should_refactor_all_occurrencs(self):
  372. ref = refactor.Refactor(self.project_root, self.filename)
  373. (change,) = ref.refactor_create_inline(self.offset, False)
  374. self.assertEqual(change['action'], 'change')
  375. self.assertEqual(change['file'], self.filename)
  376. self.assertSourceEqual(change['contents'],
  377. """\
  378. x = 2 + 3
  379. y = 17 + 4
  380. """)
  381. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  382. class TestExtractMethod(RefactorTestCase):
  383. def setUp(self):
  384. super(TestExtractMethod, self).setUp()
  385. self.filename, self.offset = self.create_file(
  386. "foo.py",
  387. """\
  388. class Foo(object):
  389. def spaghetti(self, a, b):
  390. _|_x = a + 5
  391. y = b + 23
  392. return y
  393. """)
  394. @unittest.skipIf(sys.version_info >= (3, 5), "Python 3.5 not supported")
  395. def test_should_refactor_local(self):
  396. ref = refactor.Refactor(self.project_root, self.filename)
  397. (change,) = ref.refactor_extract_method(self.offset, 104,
  398. "calc", False)
  399. self.assertEqual(change['action'], 'change')
  400. self.assertEqual(change['file'], self.filename)
  401. expected = """\
  402. class Foo(object):
  403. def spaghetti(self, a, b):
  404. return self.calc(a, b)
  405. def calc(self, a, b):
  406. x = a + 5
  407. y = b + 23
  408. return y
  409. """
  410. expected2 = expected.replace("return self.calc(a, b)",
  411. "return self.calc(b, a)")
  412. expected2 = expected2.replace("def calc(self, a, b)",
  413. "def calc(self, b, a)")
  414. # This is silly, but it's what we got.
  415. if change['contents'] == dedent(expected2):
  416. self.assertSourceEqual(change['contents'], expected2)
  417. else:
  418. self.assertSourceEqual(change['contents'], expected)
  419. @unittest.skipIf(sys.version_info >= (3, 5), "Python 3.5 not supported")
  420. def test_should_refactor_global(self):
  421. ref = refactor.Refactor(self.project_root, self.filename)
  422. (change,) = ref.refactor_extract_method(self.offset, 104,
  423. "calc", True)
  424. self.assertEqual(change['action'], 'change')
  425. self.assertEqual(change['file'], self.filename)
  426. expected = """\
  427. class Foo(object):
  428. def spaghetti(self, a, b):
  429. return calc(a, b)
  430. def calc(a, b):
  431. x = a + 5
  432. y = b + 23
  433. return y
  434. """
  435. expected2 = expected.replace("return calc(a, b)",
  436. "return calc(b, a)")
  437. expected2 = expected2.replace("def calc(a, b)",
  438. "def calc(b, a)")
  439. if change['contents'] == dedent(expected2):
  440. self.assertSourceEqual(change['contents'], expected2)
  441. else:
  442. self.assertSourceEqual(change['contents'], expected)
  443. @unittest.skipIf(not refactor.ROPE_AVAILABLE, "Requires Rope")
  444. class TestUseFunction(RefactorTestCase):
  445. def test_should_refactor(self):
  446. filename, offset = self.create_file(
  447. "foo.py",
  448. """\
  449. def _|_add_and_multiply(a, b, c):
  450. temp = a + b
  451. return temp * c
  452. f = 1 + 2
  453. g = f * 3
  454. """)
  455. ref = refactor.Refactor(self.project_root, filename)
  456. (change,) = ref.refactor_use_function(offset)
  457. self.assertEqual(change['action'], 'change')
  458. self.assertEqual(change['file'], filename)
  459. self.assertSourceEqual(change['contents'],
  460. """\
  461. def add_and_multiply(a, b, c):
  462. temp = a + b
  463. return temp * c
  464. g = add_and_multiply(1, 2, 3)
  465. """)