test_agent_skill.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. import contextlib
  2. import io
  3. import sys
  4. import docx
  5. import pytest
  6. from opendevin.runtime.plugins.agent_skills.agentskills import (
  7. create_file,
  8. edit_file,
  9. find_file,
  10. goto_line,
  11. open_file,
  12. parse_docx,
  13. parse_latex,
  14. parse_pdf,
  15. parse_pptx,
  16. scroll_down,
  17. scroll_up,
  18. search_dir,
  19. search_file,
  20. )
  21. # CURRENT_FILE must be reset for each test
  22. @pytest.fixture(autouse=True)
  23. def reset_current_file():
  24. from opendevin.runtime.plugins.agent_skills import agentskills
  25. agentskills.CURRENT_FILE = None
  26. def test_open_file_unexist_path():
  27. with pytest.raises(FileNotFoundError):
  28. open_file('/unexist/path/a.txt')
  29. def test_open_file(tmp_path):
  30. assert tmp_path is not None
  31. temp_file_path = tmp_path / 'a.txt'
  32. temp_file_path.write_text('Line 1\nLine 2\nLine 3\nLine 4\nLine 5')
  33. with io.StringIO() as buf:
  34. with contextlib.redirect_stdout(buf):
  35. open_file(str(temp_file_path))
  36. result = buf.getvalue()
  37. assert result is not None
  38. expected = (
  39. f'[File: {temp_file_path} (5 lines total)]\n'
  40. '1|Line 1\n'
  41. '2|Line 2\n'
  42. '3|Line 3\n'
  43. '4|Line 4\n'
  44. '5|Line 5\n'
  45. )
  46. assert result.split('\n') == expected.split('\n')
  47. def test_open_file_with_indentation(tmp_path):
  48. temp_file_path = tmp_path / 'a.txt'
  49. temp_file_path.write_text('Line 1\n Line 2\nLine 3\nLine 4\nLine 5')
  50. with io.StringIO() as buf:
  51. with contextlib.redirect_stdout(buf):
  52. open_file(str(temp_file_path))
  53. result = buf.getvalue()
  54. assert result is not None
  55. expected = (
  56. f'[File: {temp_file_path} (5 lines total)]\n'
  57. '1|Line 1\n'
  58. '2| Line 2\n'
  59. '3|Line 3\n'
  60. '4|Line 4\n'
  61. '5|Line 5\n'
  62. )
  63. assert result.split('\n') == expected.split('\n')
  64. def test_open_file_long(tmp_path):
  65. temp_file_path = tmp_path / 'a.txt'
  66. content = '\n'.join([f'Line {i}' for i in range(1, 1001)])
  67. temp_file_path.write_text(content)
  68. with io.StringIO() as buf:
  69. with contextlib.redirect_stdout(buf):
  70. open_file(str(temp_file_path))
  71. result = buf.getvalue()
  72. assert result is not None
  73. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  74. for i in range(1, 52):
  75. expected += f'{i}|Line {i}\n'
  76. expected += '(949 more lines below)\n'
  77. assert result.split('\n') == expected.split('\n')
  78. def test_open_file_long_with_lineno(tmp_path):
  79. temp_file_path = tmp_path / 'a.txt'
  80. content = '\n'.join([f'Line {i}' for i in range(1, 1001)])
  81. temp_file_path.write_text(content)
  82. with io.StringIO() as buf:
  83. with contextlib.redirect_stdout(buf):
  84. open_file(str(temp_file_path), 100)
  85. result = buf.getvalue()
  86. assert result is not None
  87. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  88. expected += '(50 more lines above)\n'
  89. for i in range(51, 151):
  90. expected += f'{i}|Line {i}\n'
  91. expected += '(850 more lines below)\n'
  92. assert result.split('\n') == expected.split('\n')
  93. def test_create_file_unexist_path():
  94. with pytest.raises(FileNotFoundError):
  95. create_file('/unexist/path/a.txt')
  96. def test_create_file(tmp_path):
  97. temp_file_path = tmp_path / 'a.txt'
  98. with io.StringIO() as buf:
  99. with contextlib.redirect_stdout(buf):
  100. create_file(str(temp_file_path))
  101. result = buf.getvalue()
  102. expected = (
  103. f'[File: {temp_file_path} (1 lines total)]\n'
  104. '1|\n'
  105. f'[File {temp_file_path} created.]\n'
  106. )
  107. assert result.split('\n') == expected.split('\n')
  108. def test_goto_line(tmp_path):
  109. temp_file_path = tmp_path / 'a.txt'
  110. content = '\n'.join([f'Line {i}' for i in range(1, 1001)])
  111. temp_file_path.write_text(content)
  112. with io.StringIO() as buf:
  113. with contextlib.redirect_stdout(buf):
  114. open_file(str(temp_file_path))
  115. result = buf.getvalue()
  116. assert result is not None
  117. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  118. for i in range(1, 52):
  119. expected += f'{i}|Line {i}\n'
  120. expected += '(949 more lines below)\n'
  121. assert result.split('\n') == expected.split('\n')
  122. with io.StringIO() as buf:
  123. with contextlib.redirect_stdout(buf):
  124. goto_line(100)
  125. result = buf.getvalue()
  126. assert result is not None
  127. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  128. expected += '(50 more lines above)\n'
  129. for i in range(51, 151):
  130. expected += f'{i}|Line {i}\n'
  131. expected += '(850 more lines below)\n'
  132. assert result.split('\n') == expected.split('\n')
  133. def test_goto_line_negative(tmp_path):
  134. temp_file_path = tmp_path / 'a.txt'
  135. content = '\n'.join([f'Line {i}' for i in range(1, 5)])
  136. temp_file_path.write_text(content)
  137. with io.StringIO() as buf:
  138. with contextlib.redirect_stdout(buf):
  139. open_file(str(temp_file_path))
  140. with pytest.raises(ValueError):
  141. goto_line(-1)
  142. def test_goto_line_out_of_bound(tmp_path):
  143. temp_file_path = tmp_path / 'a.txt'
  144. content = '\n'.join([f'Line {i}' for i in range(1, 5)])
  145. temp_file_path.write_text(content)
  146. with io.StringIO() as buf:
  147. with contextlib.redirect_stdout(buf):
  148. open_file(str(temp_file_path))
  149. with pytest.raises(ValueError):
  150. goto_line(100)
  151. def test_scroll_down(tmp_path):
  152. temp_file_path = tmp_path / 'a.txt'
  153. content = '\n'.join([f'Line {i}' for i in range(1, 1001)])
  154. temp_file_path.write_text(content)
  155. with io.StringIO() as buf:
  156. with contextlib.redirect_stdout(buf):
  157. open_file(str(temp_file_path))
  158. result = buf.getvalue()
  159. assert result is not None
  160. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  161. for i in range(1, 52):
  162. expected += f'{i}|Line {i}\n'
  163. expected += '(949 more lines below)\n'
  164. assert result.split('\n') == expected.split('\n')
  165. with io.StringIO() as buf:
  166. with contextlib.redirect_stdout(buf):
  167. scroll_down()
  168. result = buf.getvalue()
  169. assert result is not None
  170. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  171. expected += '(51 more lines above)\n'
  172. for i in range(52, 152):
  173. expected += f'{i}|Line {i}\n'
  174. expected += '(849 more lines below)\n'
  175. assert result.split('\n') == expected.split('\n')
  176. def test_scroll_up(tmp_path):
  177. temp_file_path = tmp_path / 'a.txt'
  178. content = '\n'.join([f'Line {i}' for i in range(1, 1001)])
  179. temp_file_path.write_text(content)
  180. with io.StringIO() as buf:
  181. with contextlib.redirect_stdout(buf):
  182. open_file(str(temp_file_path), 300)
  183. result = buf.getvalue()
  184. assert result is not None
  185. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  186. expected += '(250 more lines above)\n'
  187. for i in range(251, 351):
  188. expected += f'{i}|Line {i}\n'
  189. expected += '(650 more lines below)\n'
  190. assert result.split('\n') == expected.split('\n')
  191. with io.StringIO() as buf:
  192. with contextlib.redirect_stdout(buf):
  193. scroll_up()
  194. result = buf.getvalue()
  195. assert result is not None
  196. expected = f'[File: {temp_file_path} (1000 lines total)]\n'
  197. expected += '(150 more lines above)\n'
  198. for i in range(151, 251):
  199. expected += f'{i}|Line {i}\n'
  200. expected += '(750 more lines below)\n'
  201. assert result.split('\n') == expected.split('\n')
  202. def test_scroll_down_edge(tmp_path):
  203. temp_file_path = tmp_path / 'a.txt'
  204. content = '\n'.join([f'Line {i}' for i in range(1, 10)])
  205. temp_file_path.write_text(content)
  206. with io.StringIO() as buf:
  207. with contextlib.redirect_stdout(buf):
  208. open_file(str(temp_file_path))
  209. result = buf.getvalue()
  210. assert result is not None
  211. expected = f'[File: {temp_file_path} (9 lines total)]\n'
  212. for i in range(1, 10):
  213. expected += f'{i}|Line {i}\n'
  214. with io.StringIO() as buf:
  215. with contextlib.redirect_stdout(buf):
  216. scroll_down()
  217. result = buf.getvalue()
  218. assert result is not None
  219. # expected should be unchanged
  220. assert result.split('\n') == expected.split('\n')
  221. def test_edit_file(tmp_path):
  222. temp_file_path = tmp_path / 'a.txt'
  223. content = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5'
  224. temp_file_path.write_text(content)
  225. open_file(str(temp_file_path))
  226. with io.StringIO() as buf:
  227. with contextlib.redirect_stdout(buf):
  228. edit_file(start=1, end=3, content='REPLACE TEXT')
  229. result = buf.getvalue()
  230. expected = (
  231. f'[File: {temp_file_path} (3 lines total after edit)]\n'
  232. '1|REPLACE TEXT\n'
  233. '2|Line 4\n'
  234. '3|Line 5\n'
  235. '[File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]\n'
  236. )
  237. assert result.split('\n') == expected.split('\n')
  238. with open(temp_file_path, 'r') as file:
  239. lines = file.readlines()
  240. assert len(lines) == 3
  241. assert lines[0].rstrip() == 'REPLACE TEXT'
  242. assert lines[1].rstrip() == 'Line 4'
  243. assert lines[2].rstrip() == 'Line 5'
  244. def test_edit_file_from_scratch(tmp_path):
  245. temp_file_path = tmp_path / 'a.txt'
  246. create_file(str(temp_file_path))
  247. open_file(str(temp_file_path))
  248. with io.StringIO() as buf:
  249. with contextlib.redirect_stdout(buf):
  250. edit_file(start=1, end=1, content='REPLACE TEXT')
  251. result = buf.getvalue()
  252. expected = (
  253. f'[File: {temp_file_path} (1 lines total after edit)]\n'
  254. '1|REPLACE TEXT\n'
  255. '[File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]\n'
  256. )
  257. assert result.split('\n') == expected.split('\n')
  258. with open(temp_file_path, 'r') as file:
  259. lines = file.readlines()
  260. assert len(lines) == 1
  261. assert lines[0].rstrip() == 'REPLACE TEXT'
  262. def test_edit_file_from_scratch_multiline(tmp_path):
  263. temp_file_path = tmp_path / 'a.txt'
  264. create_file(str(temp_file_path))
  265. open_file(temp_file_path)
  266. with io.StringIO() as buf:
  267. with contextlib.redirect_stdout(buf):
  268. edit_file(
  269. start=1,
  270. end=1,
  271. content='REPLACE TEXT1\nREPLACE TEXT2\nREPLACE TEXT3',
  272. )
  273. result = buf.getvalue()
  274. expected = (
  275. f'[File: {temp_file_path} (3 lines total after edit)]\n'
  276. '1|REPLACE TEXT1\n'
  277. '2|REPLACE TEXT2\n'
  278. '3|REPLACE TEXT3\n'
  279. '[File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]\n'
  280. )
  281. assert result.split('\n') == expected.split('\n')
  282. with open(temp_file_path, 'r') as file:
  283. lines = file.readlines()
  284. assert len(lines) == 3
  285. assert lines[0].rstrip() == 'REPLACE TEXT1'
  286. assert lines[1].rstrip() == 'REPLACE TEXT2'
  287. assert lines[2].rstrip() == 'REPLACE TEXT3'
  288. def test_edit_file_not_opened():
  289. with pytest.raises(FileNotFoundError):
  290. edit_file(start=1, end=3, content='REPLACE TEXT')
  291. def test_search_dir(tmp_path):
  292. # create files with the search term "bingo"
  293. for i in range(1, 101):
  294. temp_file_path = tmp_path / f'a{i}.txt'
  295. with open(temp_file_path, 'w') as file:
  296. file.write('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n')
  297. if i == 50:
  298. file.write('bingo')
  299. # test
  300. with io.StringIO() as buf:
  301. with contextlib.redirect_stdout(buf):
  302. search_dir('bingo', str(tmp_path))
  303. result = buf.getvalue()
  304. assert result is not None
  305. expected = (
  306. f'[Found 1 matches for "bingo" in {tmp_path}]\n'
  307. f'{tmp_path}/a50.txt (Line 6): bingo\n'
  308. f'[End of matches for "bingo" in {tmp_path}]\n'
  309. )
  310. assert result.split('\n') == expected.split('\n')
  311. def test_search_dir_not_exist_term(tmp_path):
  312. # create files with the search term "bingo"
  313. for i in range(1, 101):
  314. temp_file_path = tmp_path / f'a{i}.txt'
  315. with open(temp_file_path, 'w') as file:
  316. file.write('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n')
  317. # test
  318. with io.StringIO() as buf:
  319. with contextlib.redirect_stdout(buf):
  320. search_dir('non-exist', str(tmp_path))
  321. result = buf.getvalue()
  322. assert result is not None
  323. expected = f'No matches found for "non-exist" in {tmp_path}\n'
  324. assert result.split('\n') == expected.split('\n')
  325. def test_search_dir_too_much_match(tmp_path):
  326. # create files with the search term "Line 5"
  327. for i in range(1, 1000):
  328. temp_file_path = tmp_path / f'a{i}.txt'
  329. with open(temp_file_path, 'w') as file:
  330. file.write('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n')
  331. with io.StringIO() as buf:
  332. with contextlib.redirect_stdout(buf):
  333. search_dir('Line 5', str(tmp_path))
  334. result = buf.getvalue()
  335. assert result is not None
  336. expected = f'More than 999 files matched for "Line 5" in {tmp_path}. Please narrow your search.\n'
  337. assert result.split('\n') == expected.split('\n')
  338. def test_search_dir_cwd(tmp_path, monkeypatch):
  339. # Using pytest's monkeypatch to change directory without affecting other tests
  340. monkeypatch.chdir(tmp_path)
  341. # create files with the search term "bingo"
  342. for i in range(1, 101):
  343. temp_file_path = tmp_path / f'a{i}.txt'
  344. with open(temp_file_path, 'w') as file:
  345. file.write('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n')
  346. if i == 50:
  347. file.write('bingo')
  348. with io.StringIO() as buf:
  349. with contextlib.redirect_stdout(buf):
  350. search_dir('bingo')
  351. result = buf.getvalue()
  352. assert result is not None
  353. expected = (
  354. '[Found 1 matches for "bingo" in ./]\n'
  355. './a50.txt (Line 6): bingo\n'
  356. '[End of matches for "bingo" in ./]\n'
  357. )
  358. assert result.split('\n') == expected.split('\n')
  359. def test_search_file(tmp_path):
  360. temp_file_path = tmp_path / 'a.txt'
  361. temp_file_path.write_text('Line 1\nLine 2\nLine 3\nLine 4\nLine 5')
  362. with io.StringIO() as buf:
  363. with contextlib.redirect_stdout(buf):
  364. search_file('Line 5', str(temp_file_path))
  365. result = buf.getvalue()
  366. assert result is not None
  367. expected = f'[Found 1 matches for "Line 5" in {temp_file_path}]\n'
  368. expected += 'Line 5: Line 5\n'
  369. expected += f'[End of matches for "Line 5" in {temp_file_path}]\n'
  370. assert result.split('\n') == expected.split('\n')
  371. def test_search_file_not_exist_term(tmp_path):
  372. temp_file_path = tmp_path / 'a.txt'
  373. temp_file_path.write_text('Line 1\nLine 2\nLine 3\nLine 4\nLine 5')
  374. with io.StringIO() as buf:
  375. with contextlib.redirect_stdout(buf):
  376. search_file('Line 6', str(temp_file_path))
  377. result = buf.getvalue()
  378. assert result is not None
  379. expected = f'[No matches found for "Line 6" in {temp_file_path}]\n'
  380. assert result.split('\n') == expected.split('\n')
  381. def test_search_file_not_exist_file():
  382. with pytest.raises(FileNotFoundError):
  383. search_file('Line 6', '/unexist/path/a.txt')
  384. def test_find_file(tmp_path):
  385. temp_file_path = tmp_path / 'a.txt'
  386. temp_file_path.write_text('Line 1\nLine 2\nLine 3\nLine 4\nLine 5')
  387. with io.StringIO() as buf:
  388. with contextlib.redirect_stdout(buf):
  389. find_file('a.txt', str(tmp_path))
  390. result = buf.getvalue()
  391. assert result is not None
  392. expected = f'[Found 1 matches for "a.txt" in {tmp_path}]\n'
  393. expected += f'{tmp_path}/a.txt\n'
  394. expected += f'[End of matches for "a.txt" in {tmp_path}]\n'
  395. assert result.split('\n') == expected.split('\n')
  396. def test_find_file_cwd(tmp_path, monkeypatch):
  397. monkeypatch.chdir(tmp_path)
  398. temp_file_path = tmp_path / 'a.txt'
  399. temp_file_path.write_text('Line 1\nLine 2\nLine 3\nLine 4\nLine 5')
  400. with io.StringIO() as buf:
  401. with contextlib.redirect_stdout(buf):
  402. find_file('a.txt')
  403. result = buf.getvalue()
  404. assert result is not None
  405. def test_find_file_not_exist_file():
  406. with io.StringIO() as buf:
  407. with contextlib.redirect_stdout(buf):
  408. find_file('unexist.txt')
  409. result = buf.getvalue()
  410. assert result is not None
  411. expected = '[No matches found for "unexist.txt" in ./]\n'
  412. assert result.split('\n') == expected.split('\n')
  413. def test_find_file_not_exist_file_specific_path(tmp_path):
  414. with io.StringIO() as buf:
  415. with contextlib.redirect_stdout(buf):
  416. find_file('unexist.txt', str(tmp_path))
  417. result = buf.getvalue()
  418. assert result is not None
  419. expected = f'[No matches found for "unexist.txt" in {tmp_path}]\n'
  420. assert result.split('\n') == expected.split('\n')
  421. def test_edit_lint_file_pass(tmp_path, monkeypatch):
  422. # Create a Python file with correct syntax
  423. file_path = tmp_path / 'test_file.py'
  424. file_path.write_text('\n')
  425. # patch ENABLE_AUTO_LINT
  426. monkeypatch.setattr(
  427. 'opendevin.runtime.plugins.agent_skills.agentskills.ENABLE_AUTO_LINT', True
  428. )
  429. # Test linting functionality
  430. with io.StringIO() as buf:
  431. with contextlib.redirect_stdout(buf):
  432. open_file(str(file_path))
  433. edit_file(1, 1, "print('hello')\n")
  434. result = buf.getvalue()
  435. assert result is not None
  436. expected = (
  437. f'[File: {file_path} (1 lines total)]\n'
  438. '1|\n'
  439. f'[File: {file_path} (2 lines total after edit)]\n'
  440. "1|print('hello')\n"
  441. '2|\n'
  442. '[File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]\n'
  443. )
  444. assert result.split('\n') == expected.split('\n')
  445. def test_lint_file_fail_undefined_name(tmp_path, monkeypatch, capsys):
  446. # Create a Python file with a syntax error
  447. file_path = tmp_path / 'test_file.py'
  448. file_path.write_text('\n')
  449. # Set environment variable to enable linting
  450. monkeypatch.setattr(
  451. 'opendevin.runtime.plugins.agent_skills.agentskills.ENABLE_AUTO_LINT', True
  452. )
  453. open_file(str(file_path))
  454. edit_file(1, 1, 'undefined_name()\n')
  455. result = capsys.readouterr().out
  456. print(result)
  457. assert result is not None
  458. expected = (
  459. f'[File: {file_path} (1 lines total)]\n'
  460. '1|\n'
  461. '[Your proposed edit has introduced new syntax error(s). Please understand the errors and retry your edit command.]\n'
  462. 'ERRORS:\n'
  463. f"{file_path}:1:1: F821 undefined name 'undefined_name'\n"
  464. '[This is how your edit would have looked if applied]\n'
  465. '-------------------------------------------------\n'
  466. '1|undefined_name()\n'
  467. '2|\n'
  468. '-------------------------------------------------\n\n'
  469. '[This is the original code before your edit]\n'
  470. '-------------------------------------------------\n'
  471. '1|\n'
  472. '-------------------------------------------------\n'
  473. 'Your changes have NOT been applied. Please fix your edit command and try again.\n'
  474. 'You either need to 1) Specify the correct start/end line arguments or 2) Correct your edit code.\n'
  475. 'DO NOT re-run the same failed edit command. Running it again will lead to the same error.\n'
  476. )
  477. assert result.split('\n') == expected.split('\n')
  478. def test_lint_file_fail_undefined_name_long(tmp_path, monkeypatch, capsys):
  479. # Create a Python file with a syntax error
  480. file_path = tmp_path / 'test_file.py'
  481. file_path.write_text('\n' * 1000)
  482. # Set environment variable to enable linting
  483. monkeypatch.setattr(
  484. 'opendevin.runtime.plugins.agent_skills.agentskills.ENABLE_AUTO_LINT', True
  485. )
  486. open_file(str(file_path))
  487. edit_file(500, 500, 'undefined_name()\n')
  488. result = capsys.readouterr().out
  489. print(result)
  490. assert result is not None
  491. open_lines = '\n'.join([f'{i+1}|' for i in range(51)])
  492. expected = (
  493. f'[File: {file_path} (1000 lines total)]\n'
  494. f'{open_lines}\n'
  495. '(949 more lines below)\n'
  496. '[Your proposed edit has introduced new syntax error(s). Please understand the errors and retry your edit command.]\n'
  497. 'ERRORS:\n'
  498. f"{file_path}:500:1: F821 undefined name 'undefined_name'\n"
  499. '[This is how your edit would have looked if applied]\n'
  500. '-------------------------------------------------\n'
  501. '(496 more lines above)\n'
  502. '497|\n'
  503. '498|\n'
  504. '499|\n'
  505. '500|undefined_name()\n'
  506. '501|\n'
  507. '502|\n'
  508. '503|\n'
  509. '504|\n'
  510. '505|\n'
  511. '506|\n'
  512. '(495 more lines below)\n'
  513. '-------------------------------------------------\n\n'
  514. '[This is the original code before your edit]\n'
  515. '-------------------------------------------------\n'
  516. '(496 more lines above)\n'
  517. '497|\n'
  518. '498|\n'
  519. '499|\n'
  520. '500|\n'
  521. '501|\n'
  522. '502|\n'
  523. '503|\n'
  524. '504|\n'
  525. '505|\n'
  526. '506|\n'
  527. '(494 more lines below)\n'
  528. '-------------------------------------------------\n'
  529. 'Your changes have NOT been applied. Please fix your edit command and try again.\n'
  530. 'You either need to 1) Specify the correct start/end line arguments or 2) Correct your edit code.\n'
  531. 'DO NOT re-run the same failed edit command. Running it again will lead to the same error.\n'
  532. )
  533. assert result.split('\n') == expected.split('\n')
  534. def test_lint_file_disabled_undefined_name(tmp_path, monkeypatch, capsys):
  535. # Create a Python file with a syntax error
  536. file_path = tmp_path / 'test_file.py'
  537. file_path.write_text('\n')
  538. # Set environment variable to enable linting
  539. monkeypatch.setattr(
  540. 'opendevin.runtime.plugins.agent_skills.agentskills.ENABLE_AUTO_LINT', False
  541. )
  542. open_file(str(file_path))
  543. edit_file(1, 1, 'undefined_name()\n')
  544. result = capsys.readouterr().out
  545. assert result is not None
  546. expected = (
  547. f'[File: {file_path} (1 lines total)]\n'
  548. '1|\n'
  549. f'[File: {file_path} (2 lines total after edit)]\n'
  550. '1|undefined_name()\n'
  551. '2|\n'
  552. '[File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]\n'
  553. )
  554. assert result.split('\n') == expected.split('\n')
  555. def test_parse_docx(tmp_path):
  556. # Create a DOCX file with some content
  557. test_docx_path = tmp_path / 'test.docx'
  558. doc = docx.Document()
  559. doc.add_paragraph('Hello, this is a test document.')
  560. doc.add_paragraph('This is the second paragraph.')
  561. doc.save(str(test_docx_path))
  562. old_stdout = sys.stdout
  563. sys.stdout = io.StringIO()
  564. # Call the parse_docx function
  565. parse_docx(str(test_docx_path))
  566. # Capture the output
  567. output = sys.stdout.getvalue()
  568. sys.stdout = old_stdout
  569. # Check if the output is correct
  570. expected_output = (
  571. f'[Reading DOCX file from {test_docx_path}]\n'
  572. '@@ Page 1 @@\nHello, this is a test document.\n\n'
  573. '@@ Page 2 @@\nThis is the second paragraph.\n\n\n'
  574. )
  575. assert output == expected_output, f'Expected output does not match. Got: {output}'
  576. def test_parse_latex(tmp_path):
  577. # Create a LaTeX file with some content
  578. test_latex_path = tmp_path / 'test.tex'
  579. with open(test_latex_path, 'w') as f:
  580. f.write(r"""
  581. \documentclass{article}
  582. \begin{document}
  583. Hello, this is a test LaTeX document.
  584. \end{document}
  585. """)
  586. old_stdout = sys.stdout
  587. sys.stdout = io.StringIO()
  588. # Call the parse_latex function
  589. parse_latex(str(test_latex_path))
  590. # Capture the output
  591. output = sys.stdout.getvalue()
  592. sys.stdout = old_stdout
  593. # Check if the output is correct
  594. expected_output = (
  595. f'[Reading LaTex file from {test_latex_path}]\n'
  596. 'Hello, this is a test LaTeX document.\n'
  597. )
  598. assert output == expected_output, f'Expected output does not match. Got: {output}'
  599. def test_parse_pdf(tmp_path):
  600. # Create a PDF file with some content
  601. test_pdf_path = tmp_path / 'test.pdf'
  602. from reportlab.lib.pagesizes import letter
  603. from reportlab.pdfgen import canvas
  604. c = canvas.Canvas(str(test_pdf_path), pagesize=letter)
  605. c.drawString(100, 750, 'Hello, this is a test PDF document.')
  606. c.save()
  607. old_stdout = sys.stdout
  608. sys.stdout = io.StringIO()
  609. # Call the parse_pdf function
  610. parse_pdf(str(test_pdf_path))
  611. # Capture the output
  612. output = sys.stdout.getvalue()
  613. sys.stdout = old_stdout
  614. # Check if the output is correct
  615. expected_output = (
  616. f'[Reading PDF file from {test_pdf_path}]\n'
  617. '@@ Page 1 @@\n'
  618. 'Hello, this is a test PDF document.\n'
  619. )
  620. assert output == expected_output, f'Expected output does not match. Got: {output}'
  621. def test_parse_pptx(tmp_path):
  622. test_pptx_path = tmp_path / 'test.pptx'
  623. from pptx import Presentation
  624. pres = Presentation()
  625. slide1 = pres.slides.add_slide(pres.slide_layouts[0])
  626. title1 = slide1.shapes.title
  627. title1.text = 'Hello, this is the first test PPTX slide.'
  628. slide2 = pres.slides.add_slide(pres.slide_layouts[0])
  629. title2 = slide2.shapes.title
  630. title2.text = 'Hello, this is the second test PPTX slide.'
  631. pres.save(str(test_pptx_path))
  632. old_stdout = sys.stdout
  633. sys.stdout = io.StringIO()
  634. parse_pptx(str(test_pptx_path))
  635. output = sys.stdout.getvalue()
  636. sys.stdout = old_stdout
  637. expected_output = (
  638. f'[Reading PowerPoint file from {test_pptx_path}]\n'
  639. '@@ Slide 1 @@\n'
  640. 'Hello, this is the first test PPTX slide.\n\n'
  641. '@@ Slide 2 @@\n'
  642. 'Hello, this is the second test PPTX slide.\n\n'
  643. )
  644. assert output == expected_output, f'Expected output does not match. Got: {output}'