test_aider_linter.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import os
  2. from unittest.mock import patch
  3. import pytest
  4. from openhands.runtime.plugins.agent_skills.utils.aider import Linter, LintResult
  5. @pytest.fixture
  6. def temp_file(tmp_path):
  7. # Fixture to create a temporary file
  8. temp_name = os.path.join(tmp_path, 'lint-test.py')
  9. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  10. tmp_file.write("""def foo():
  11. print("Hello, World!")
  12. foo()
  13. """)
  14. tmp_file.close()
  15. yield temp_name
  16. os.remove(temp_name)
  17. @pytest.fixture
  18. def temp_ruby_file_errors(tmp_path):
  19. # Fixture to create a temporary file
  20. temp_name = os.path.join(tmp_path, 'lint-test.rb')
  21. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  22. tmp_file.write("""def foo():
  23. print("Hello, World!")
  24. foo()
  25. """)
  26. tmp_file.close()
  27. yield temp_name
  28. os.remove(temp_name)
  29. @pytest.fixture
  30. def temp_ruby_file_errors_parentheses(tmp_path):
  31. # Fixture to create a temporary file
  32. temp_name = os.path.join(tmp_path, 'lint-test.rb')
  33. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  34. tmp_file.write("""def print_hello_world()\n puts 'Hello World'\n""")
  35. tmp_file.close()
  36. yield temp_name
  37. os.remove(temp_name)
  38. @pytest.fixture
  39. def temp_ruby_file_correct(tmp_path):
  40. # Fixture to create a temporary file
  41. temp_name = os.path.join(tmp_path, 'lint-test.rb')
  42. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  43. tmp_file.write("""def foo
  44. puts "Hello, World!"
  45. end
  46. foo
  47. """)
  48. tmp_file.close()
  49. yield temp_name
  50. os.remove(temp_name)
  51. @pytest.fixture
  52. def linter(tmp_path):
  53. return Linter(root=tmp_path)
  54. @pytest.fixture
  55. def temp_typescript_file_errors(tmp_path):
  56. # Fixture to create a temporary TypeScript file with errors
  57. temp_name = os.path.join(tmp_path, 'lint-test.ts')
  58. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  59. tmp_file.write("""function foo() {
  60. console.log("Hello, World!")
  61. foo()
  62. """)
  63. tmp_file.close()
  64. yield temp_name
  65. os.remove(temp_name)
  66. @pytest.fixture
  67. def temp_typescript_file_errors_semicolon(tmp_path):
  68. # Fixture to create a temporary TypeScript file with missing semicolon
  69. temp_name = os.path.join(tmp_path, 'lint-test.ts')
  70. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  71. tmp_file.write("""function printHelloWorld() {
  72. console.log('Hello World')
  73. }""")
  74. tmp_file.close()
  75. yield temp_name
  76. os.remove(temp_name)
  77. @pytest.fixture
  78. def temp_typescript_file_correct(tmp_path):
  79. # Fixture to create a temporary TypeScript file with correct code
  80. temp_name = os.path.join(tmp_path, 'lint-test.ts')
  81. with open(temp_name, 'w', encoding='utf-8') as tmp_file:
  82. tmp_file.write("""function foo(): void {
  83. console.log("Hello, World!");
  84. }
  85. foo();
  86. """)
  87. tmp_file.close()
  88. yield temp_name
  89. os.remove(temp_name)
  90. def test_get_rel_fname(linter, temp_file, tmp_path):
  91. # Test get_rel_fname method
  92. rel_fname = linter.get_rel_fname(temp_file)
  93. assert rel_fname == os.path.relpath(temp_file, tmp_path)
  94. def test_run_cmd(linter, temp_file):
  95. # Test run_cmd method with a simple command
  96. result = linter.run_cmd('echo', temp_file, '')
  97. assert result is None # echo command should return zero exit status
  98. def test_set_linter(linter):
  99. # Test set_linter method
  100. def custom_linter(fname, rel_fname, code):
  101. return LintResult(text='Custom Linter', lines=[1])
  102. linter.set_linter('custom', custom_linter)
  103. assert 'custom' in linter.languages
  104. assert linter.languages['custom'] == custom_linter
  105. def test_py_lint(linter, temp_file):
  106. # Test py_lint method
  107. result = linter.py_lint(
  108. temp_file, linter.get_rel_fname(temp_file), "print('Hello, World!')\n"
  109. )
  110. assert result is None # No lint errors expected for this simple code
  111. def test_py_lint_fail(linter, temp_file):
  112. # Test py_lint method
  113. result = linter.py_lint(
  114. temp_file, linter.get_rel_fname(temp_file), "print('Hello, World!')\n"
  115. )
  116. assert result is None
  117. def test_basic_lint(temp_file):
  118. from openhands.runtime.plugins.agent_skills.utils.aider.linter import basic_lint
  119. poorly_formatted_code = """
  120. def foo()
  121. print("Hello, World!")
  122. print("Wrong indent")
  123. foo(
  124. """
  125. result = basic_lint(temp_file, poorly_formatted_code)
  126. assert isinstance(result, LintResult)
  127. assert result.text.startswith(f'{temp_file}:2:9')
  128. assert 2 in result.lines
  129. def test_basic_lint_fail_returns_text_and_lines(temp_file):
  130. from openhands.runtime.plugins.agent_skills.utils.aider.linter import basic_lint
  131. poorly_formatted_code = """
  132. def foo()
  133. print("Hello, World!")
  134. print("Wrong indent")
  135. foo(
  136. """
  137. result = basic_lint(temp_file, poorly_formatted_code)
  138. assert isinstance(result, LintResult)
  139. assert result.text.startswith(f'{temp_file}:2:9')
  140. assert 2 in result.lines
  141. def test_lint_python_compile(temp_file):
  142. from openhands.runtime.plugins.agent_skills.utils.aider.linter import (
  143. lint_python_compile,
  144. )
  145. result = lint_python_compile(temp_file, "print('Hello, World!')\n")
  146. assert result is None
  147. def test_lint_python_compile_fail_returns_text_and_lines(temp_file):
  148. from openhands.runtime.plugins.agent_skills.utils.aider.linter import (
  149. lint_python_compile,
  150. )
  151. poorly_formatted_code = """
  152. def foo()
  153. print("Hello, World!")
  154. print("Wrong indent")
  155. foo(
  156. """
  157. result = lint_python_compile(temp_file, poorly_formatted_code)
  158. assert temp_file in result.text
  159. assert 1 in result.lines
  160. def test_lint(linter, temp_file):
  161. result = linter.lint(temp_file)
  162. assert result is None
  163. def test_lint_fail(linter, temp_file):
  164. # Test lint method
  165. with open(temp_file, 'w', encoding='utf-8') as lint_file:
  166. lint_file.write("""
  167. def foo()
  168. print("Hello, World!")
  169. print("Wrong indent")
  170. foo(
  171. """)
  172. errors = linter.lint(temp_file)
  173. assert errors is not None
  174. def test_lint_pass_ruby(linter, temp_ruby_file_correct):
  175. result = linter.lint(temp_ruby_file_correct)
  176. assert result is None
  177. def test_lint_fail_ruby(linter, temp_ruby_file_errors):
  178. errors = linter.lint(temp_ruby_file_errors)
  179. assert errors is not None
  180. def test_lint_fail_ruby_no_parentheses(linter, temp_ruby_file_errors_parentheses):
  181. errors = linter.lint(temp_ruby_file_errors_parentheses)
  182. assert errors is not None
  183. def test_lint_pass_typescript(linter, temp_typescript_file_correct):
  184. if linter.ts_installed:
  185. result = linter.lint(temp_typescript_file_correct)
  186. assert result is None
  187. def test_lint_fail_typescript(linter, temp_typescript_file_errors):
  188. if linter.ts_installed:
  189. errors = linter.lint(temp_typescript_file_errors)
  190. assert errors is not None
  191. def test_lint_fail_typescript_missing_semicolon(
  192. linter, temp_typescript_file_errors_semicolon
  193. ):
  194. if linter.ts_installed:
  195. with patch.dict(os.environ, {'ENABLE_AUTO_LINT': 'True'}):
  196. errors = linter.lint(temp_typescript_file_errors_semicolon)
  197. assert errors is not None