test_bash.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. """Bash-related tests for the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
  2. import os
  3. import tempfile
  4. import time
  5. import pytest
  6. from conftest import _load_runtime
  7. from openhands.core.logger import openhands_logger as logger
  8. from openhands.events.action import CmdRunAction
  9. from openhands.events.observation import CmdOutputObservation
  10. # ============================================================================================================================
  11. # Bash-specific tests
  12. # ============================================================================================================================
  13. def test_bash_command_pexcept(temp_dir, box_class, run_as_openhands):
  14. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  15. # We set env var PS1="\u@\h:\w $"
  16. # and construct the PEXCEPT prompt base on it.
  17. # When run `env`, bad implementation of CmdRunAction will be pexcepted by this
  18. # and failed to pexcept the right content, causing it fail to get error code.
  19. obs = runtime.run_action(CmdRunAction(command='env'))
  20. # For example:
  21. # 02:16:13 - openhands:DEBUG: client.py:78 - Executing command: env
  22. # 02:16:13 - openhands:DEBUG: client.py:82 - Command output: PYTHONUNBUFFERED=1
  23. # CONDA_EXE=/openhands/miniforge3/bin/conda
  24. # [...]
  25. # LC_CTYPE=C.UTF-8
  26. # PS1=\u@\h:\w $
  27. # 02:16:13 - openhands:DEBUG: client.py:89 - Executing command for exit code: env
  28. # 02:16:13 - openhands:DEBUG: client.py:92 - Exit code Output:
  29. # CONDA_DEFAULT_ENV=base
  30. # As long as the exit code is 0, the test will pass.
  31. assert isinstance(
  32. obs, CmdOutputObservation
  33. ), 'The observation should be a CmdOutputObservation.'
  34. assert obs.exit_code == 0, 'The exit code should be 0.'
  35. runtime.close(rm_all_containers=False)
  36. time.sleep(1)
  37. def test_single_multiline_command(temp_dir, box_class):
  38. runtime = _load_runtime(temp_dir, box_class)
  39. action = CmdRunAction(command='echo \\\n -e "foo"')
  40. logger.info(action, extra={'msg_type': 'ACTION'})
  41. obs = runtime.run_action(action)
  42. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  43. assert obs.exit_code == 0, 'The exit code should be 0.'
  44. assert 'foo' in obs.content
  45. runtime.close(rm_all_containers=False)
  46. time.sleep(1)
  47. def test_multiline_echo(temp_dir, box_class):
  48. runtime = _load_runtime(temp_dir, box_class)
  49. action = CmdRunAction(command='echo -e "hello\nworld"')
  50. logger.info(action, extra={'msg_type': 'ACTION'})
  51. obs = runtime.run_action(action)
  52. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  53. assert obs.exit_code == 0, 'The exit code should be 0.'
  54. assert 'hello\r\nworld' in obs.content
  55. runtime.close(rm_all_containers=False)
  56. time.sleep(1)
  57. def test_runtime_whitespace(temp_dir, box_class):
  58. runtime = _load_runtime(temp_dir, box_class)
  59. action = CmdRunAction(command='echo -e "\\n\\n\\n"')
  60. logger.info(action, extra={'msg_type': 'ACTION'})
  61. obs = runtime.run_action(action)
  62. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  63. assert obs.exit_code == 0, 'The exit code should be 0.'
  64. assert '\r\n\r\n\r\n' in obs.content
  65. runtime.close(rm_all_containers=False)
  66. time.sleep(1)
  67. def test_multiple_multiline_commands(temp_dir, box_class, run_as_openhands):
  68. cmds = [
  69. 'ls -l',
  70. 'echo -e "hello\nworld"',
  71. """
  72. echo -e "hello it\\'s me"
  73. """.strip(),
  74. """
  75. echo \\
  76. -e 'hello' \\
  77. -v
  78. """.strip(),
  79. """
  80. echo -e 'hello\\nworld\\nare\\nyou\\nthere?'
  81. """.strip(),
  82. """
  83. echo -e 'hello
  84. world
  85. are
  86. you\\n
  87. there?'
  88. """.strip(),
  89. """
  90. echo -e 'hello
  91. world "
  92. '
  93. """.strip(),
  94. ]
  95. joined_cmds = '\n'.join(cmds)
  96. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  97. action = CmdRunAction(command=joined_cmds)
  98. logger.info(action, extra={'msg_type': 'ACTION'})
  99. obs = runtime.run_action(action)
  100. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  101. assert isinstance(obs, CmdOutputObservation)
  102. assert obs.exit_code == 0, 'The exit code should be 0.'
  103. assert 'total 0' in obs.content
  104. assert 'hello\r\nworld' in obs.content
  105. assert "hello it\\'s me" in obs.content
  106. assert 'hello -v' in obs.content
  107. assert 'hello\r\nworld\r\nare\r\nyou\r\nthere?' in obs.content
  108. assert 'hello\r\nworld\r\nare\r\nyou\r\n\r\nthere?' in obs.content
  109. assert 'hello\r\nworld "\r\n' in obs.content
  110. runtime.close(rm_all_containers=False)
  111. time.sleep(1)
  112. def test_no_ps2_in_output(temp_dir, box_class, run_as_openhands):
  113. """Test that the PS2 sign is not added to the output of a multiline command."""
  114. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  115. action = CmdRunAction(command='echo -e "hello\nworld"')
  116. logger.info(action, extra={'msg_type': 'ACTION'})
  117. obs = runtime.run_action(action)
  118. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  119. assert 'hello\r\nworld' in obs.content
  120. assert '>' not in obs.content
  121. runtime.close(rm_all_containers=False)
  122. time.sleep(1)
  123. def test_multiline_command_loop(temp_dir, box_class):
  124. # https://github.com/All-Hands-AI/OpenHands/issues/3143
  125. runtime = _load_runtime(temp_dir, box_class)
  126. init_cmd = """
  127. mkdir -p _modules && \
  128. for month in {01..04}; do
  129. for day in {01..05}; do
  130. touch "_modules/2024-${month}-${day}-sample.md"
  131. done
  132. done
  133. echo "created files"
  134. """
  135. action = CmdRunAction(command=init_cmd)
  136. logger.info(action, extra={'msg_type': 'ACTION'})
  137. obs = runtime.run_action(action)
  138. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  139. assert isinstance(obs, CmdOutputObservation)
  140. assert obs.exit_code == 0, 'The exit code should be 0.'
  141. assert 'created files' in obs.content
  142. follow_up_cmd = """
  143. for file in _modules/*.md; do
  144. new_date=$(echo $file | sed -E 's/2024-(01|02|03|04)-/2024-/;s/2024-01/2024-08/;s/2024-02/2024-09/;s/2024-03/2024-10/;s/2024-04/2024-11/')
  145. mv "$file" "$new_date"
  146. done
  147. echo "success"
  148. """
  149. action = CmdRunAction(command=follow_up_cmd)
  150. logger.info(action, extra={'msg_type': 'ACTION'})
  151. obs = runtime.run_action(action)
  152. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  153. assert isinstance(obs, CmdOutputObservation)
  154. assert obs.exit_code == 0, 'The exit code should be 0.'
  155. assert 'success' in obs.content
  156. runtime.close(rm_all_containers=False)
  157. time.sleep(1)
  158. def test_cmd_run(temp_dir, box_class, run_as_openhands):
  159. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  160. action = CmdRunAction(command='ls -l')
  161. logger.info(action, extra={'msg_type': 'ACTION'})
  162. obs = runtime.run_action(action)
  163. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  164. assert isinstance(obs, CmdOutputObservation)
  165. assert obs.exit_code == 0
  166. assert 'total 0' in obs.content
  167. action = CmdRunAction(command='mkdir test')
  168. logger.info(action, extra={'msg_type': 'ACTION'})
  169. obs = runtime.run_action(action)
  170. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  171. assert isinstance(obs, CmdOutputObservation)
  172. assert obs.exit_code == 0
  173. action = CmdRunAction(command='ls -l')
  174. logger.info(action, extra={'msg_type': 'ACTION'})
  175. obs = runtime.run_action(action)
  176. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  177. assert isinstance(obs, CmdOutputObservation)
  178. assert obs.exit_code == 0
  179. if run_as_openhands:
  180. assert 'openhands' in obs.content
  181. else:
  182. assert 'root' in obs.content
  183. assert 'test' in obs.content
  184. action = CmdRunAction(command='touch test/foo.txt')
  185. logger.info(action, extra={'msg_type': 'ACTION'})
  186. obs = runtime.run_action(action)
  187. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  188. assert isinstance(obs, CmdOutputObservation)
  189. assert obs.exit_code == 0
  190. action = CmdRunAction(command='ls -l test')
  191. logger.info(action, extra={'msg_type': 'ACTION'})
  192. obs = runtime.run_action(action)
  193. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  194. assert isinstance(obs, CmdOutputObservation)
  195. assert obs.exit_code == 0
  196. assert 'foo.txt' in obs.content
  197. # clean up: this is needed, since CI will not be
  198. # run as root, and this test may leave a file
  199. # owned by root
  200. action = CmdRunAction(command='rm -rf test')
  201. logger.info(action, extra={'msg_type': 'ACTION'})
  202. obs = runtime.run_action(action)
  203. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  204. assert isinstance(obs, CmdOutputObservation)
  205. assert obs.exit_code == 0
  206. runtime.close(rm_all_containers=False)
  207. time.sleep(1)
  208. def test_run_as_user_correct_home_dir(temp_dir, box_class, run_as_openhands):
  209. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  210. action = CmdRunAction(command='cd ~ && pwd')
  211. logger.info(action, extra={'msg_type': 'ACTION'})
  212. obs = runtime.run_action(action)
  213. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  214. assert isinstance(obs, CmdOutputObservation)
  215. assert obs.exit_code == 0
  216. if run_as_openhands:
  217. assert '/home/openhands' in obs.content
  218. else:
  219. assert '/root' in obs.content
  220. runtime.close(rm_all_containers=False)
  221. time.sleep(1)
  222. def test_multi_cmd_run_in_single_line(temp_dir, box_class):
  223. runtime = _load_runtime(temp_dir, box_class)
  224. action = CmdRunAction(command='pwd && ls -l')
  225. logger.info(action, extra={'msg_type': 'ACTION'})
  226. obs = runtime.run_action(action)
  227. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  228. assert isinstance(obs, CmdOutputObservation)
  229. assert obs.exit_code == 0
  230. assert '/workspace' in obs.content
  231. assert 'total 0' in obs.content
  232. runtime.close(rm_all_containers=False)
  233. time.sleep(1)
  234. def test_stateful_cmd(temp_dir, box_class):
  235. runtime = _load_runtime(temp_dir, box_class)
  236. action = CmdRunAction(command='mkdir test')
  237. logger.info(action, extra={'msg_type': 'ACTION'})
  238. obs = runtime.run_action(action)
  239. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  240. assert isinstance(obs, CmdOutputObservation)
  241. assert obs.exit_code == 0, 'The exit code should be 0.'
  242. action = CmdRunAction(command='cd test')
  243. logger.info(action, extra={'msg_type': 'ACTION'})
  244. obs = runtime.run_action(action)
  245. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  246. assert isinstance(obs, CmdOutputObservation)
  247. assert obs.exit_code == 0, 'The exit code should be 0.'
  248. action = CmdRunAction(command='pwd')
  249. logger.info(action, extra={'msg_type': 'ACTION'})
  250. obs = runtime.run_action(action)
  251. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  252. assert isinstance(obs, CmdOutputObservation)
  253. assert obs.exit_code == 0, 'The exit code should be 0.'
  254. assert '/workspace/test' in obs.content
  255. runtime.close(rm_all_containers=False)
  256. time.sleep(1)
  257. def test_failed_cmd(temp_dir, box_class):
  258. runtime = _load_runtime(temp_dir, box_class)
  259. action = CmdRunAction(command='non_existing_command')
  260. logger.info(action, extra={'msg_type': 'ACTION'})
  261. obs = runtime.run_action(action)
  262. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  263. assert isinstance(obs, CmdOutputObservation)
  264. assert obs.exit_code != 0, 'The exit code should not be 0 for a failed command.'
  265. runtime.close(rm_all_containers=False)
  266. time.sleep(1)
  267. def _create_test_file(host_temp_dir):
  268. # Single file
  269. with open(os.path.join(host_temp_dir, 'test_file.txt'), 'w') as f:
  270. f.write('Hello, World!')
  271. def test_copy_single_file(temp_dir, box_class):
  272. runtime = _load_runtime(temp_dir, box_class)
  273. with tempfile.TemporaryDirectory() as host_temp_dir:
  274. _create_test_file(host_temp_dir)
  275. runtime.copy_to(os.path.join(host_temp_dir, 'test_file.txt'), '/workspace')
  276. action = CmdRunAction(command='ls -alh /workspace')
  277. logger.info(action, extra={'msg_type': 'ACTION'})
  278. obs = runtime.run_action(action)
  279. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  280. assert isinstance(obs, CmdOutputObservation)
  281. assert obs.exit_code == 0
  282. assert 'test_file.txt' in obs.content
  283. action = CmdRunAction(command='cat /workspace/test_file.txt')
  284. logger.info(action, extra={'msg_type': 'ACTION'})
  285. obs = runtime.run_action(action)
  286. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  287. assert isinstance(obs, CmdOutputObservation)
  288. assert obs.exit_code == 0
  289. assert 'Hello, World!' in obs.content
  290. runtime.close(rm_all_containers=False)
  291. time.sleep(1)
  292. def _create_test_dir_with_files(host_temp_dir):
  293. os.mkdir(os.path.join(host_temp_dir, 'test_dir'))
  294. with open(os.path.join(host_temp_dir, 'test_dir', 'file1.txt'), 'w') as f:
  295. f.write('File 1 content')
  296. with open(os.path.join(host_temp_dir, 'test_dir', 'file2.txt'), 'w') as f:
  297. f.write('File 2 content')
  298. def test_copy_directory_recursively(temp_dir, box_class):
  299. runtime = _load_runtime(temp_dir, box_class)
  300. with tempfile.TemporaryDirectory() as host_temp_dir:
  301. # We need a separate directory, since temp_dir is mounted to /workspace
  302. _create_test_dir_with_files(host_temp_dir)
  303. runtime.copy_to(
  304. os.path.join(host_temp_dir, 'test_dir'), '/workspace', recursive=True
  305. )
  306. action = CmdRunAction(command='ls -alh /workspace')
  307. logger.info(action, extra={'msg_type': 'ACTION'})
  308. obs = runtime.run_action(action)
  309. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  310. assert isinstance(obs, CmdOutputObservation)
  311. assert obs.exit_code == 0
  312. assert 'test_dir' in obs.content
  313. assert 'file1.txt' not in obs.content
  314. assert 'file2.txt' not in obs.content
  315. action = CmdRunAction(command='ls -alh /workspace/test_dir')
  316. logger.info(action, extra={'msg_type': 'ACTION'})
  317. obs = runtime.run_action(action)
  318. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  319. assert isinstance(obs, CmdOutputObservation)
  320. assert obs.exit_code == 0
  321. assert 'file1.txt' in obs.content
  322. assert 'file2.txt' in obs.content
  323. action = CmdRunAction(command='cat /workspace/test_dir/file1.txt')
  324. logger.info(action, extra={'msg_type': 'ACTION'})
  325. obs = runtime.run_action(action)
  326. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  327. assert isinstance(obs, CmdOutputObservation)
  328. assert obs.exit_code == 0
  329. assert 'File 1 content' in obs.content
  330. runtime.close(rm_all_containers=False)
  331. time.sleep(1)
  332. def test_copy_to_non_existent_directory(temp_dir, box_class):
  333. runtime = _load_runtime(temp_dir, box_class)
  334. with tempfile.TemporaryDirectory() as host_temp_dir:
  335. _create_test_file(host_temp_dir)
  336. runtime.copy_to(
  337. os.path.join(host_temp_dir, 'test_file.txt'), '/workspace/new_dir'
  338. )
  339. action = CmdRunAction(command='cat /workspace/new_dir/test_file.txt')
  340. logger.info(action, extra={'msg_type': 'ACTION'})
  341. obs = runtime.run_action(action)
  342. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  343. assert isinstance(obs, CmdOutputObservation)
  344. assert obs.exit_code == 0
  345. assert 'Hello, World!' in obs.content
  346. runtime.close(rm_all_containers=False)
  347. time.sleep(1)
  348. def test_overwrite_existing_file(temp_dir, box_class):
  349. runtime = _load_runtime(temp_dir, box_class)
  350. # touch a file in /workspace
  351. action = CmdRunAction(command='touch /workspace/test_file.txt')
  352. logger.info(action, extra={'msg_type': 'ACTION'})
  353. obs = runtime.run_action(action)
  354. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  355. assert isinstance(obs, CmdOutputObservation)
  356. assert obs.exit_code == 0
  357. action = CmdRunAction(command='cat /workspace/test_file.txt')
  358. logger.info(action, extra={'msg_type': 'ACTION'})
  359. obs = runtime.run_action(action)
  360. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  361. assert isinstance(obs, CmdOutputObservation)
  362. assert obs.exit_code == 0
  363. assert 'Hello, World!' not in obs.content
  364. with tempfile.TemporaryDirectory() as host_temp_dir:
  365. _create_test_file(host_temp_dir)
  366. runtime.copy_to(os.path.join(host_temp_dir, 'test_file.txt'), '/workspace')
  367. action = CmdRunAction(command='cat /workspace/test_file.txt')
  368. logger.info(action, extra={'msg_type': 'ACTION'})
  369. obs = runtime.run_action(action)
  370. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  371. assert isinstance(obs, CmdOutputObservation)
  372. assert obs.exit_code == 0
  373. assert 'Hello, World!' in obs.content
  374. runtime.close(rm_all_containers=False)
  375. time.sleep(1)
  376. def test_copy_non_existent_file(temp_dir, box_class):
  377. runtime = _load_runtime(temp_dir, box_class)
  378. with pytest.raises(FileNotFoundError):
  379. runtime.copy_to(
  380. os.path.join(temp_dir, 'non_existent_file.txt'),
  381. '/workspace/should_not_exist.txt',
  382. )
  383. action = CmdRunAction(command='ls /workspace/should_not_exist.txt')
  384. logger.info(action, extra={'msg_type': 'ACTION'})
  385. obs = runtime.run_action(action)
  386. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  387. assert isinstance(obs, CmdOutputObservation)
  388. assert obs.exit_code != 0 # File should not exist
  389. runtime.close(rm_all_containers=False)
  390. time.sleep(1)
  391. def test_keep_prompt(box_class, temp_dir):
  392. runtime = _load_runtime(
  393. temp_dir,
  394. box_class=box_class,
  395. run_as_openhands=False,
  396. )
  397. action = CmdRunAction(command='touch /workspace/test_file.txt')
  398. logger.info(action, extra={'msg_type': 'ACTION'})
  399. obs = runtime.run_action(action)
  400. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  401. assert isinstance(obs, CmdOutputObservation)
  402. assert obs.exit_code == 0
  403. assert 'root@' in obs.content
  404. action = CmdRunAction(command='cat /workspace/test_file.txt', keep_prompt=False)
  405. logger.info(action, extra={'msg_type': 'ACTION'})
  406. obs = runtime.run_action(action)
  407. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  408. assert isinstance(obs, CmdOutputObservation)
  409. assert obs.exit_code == 0
  410. assert 'root@' not in obs.content
  411. runtime.close(rm_all_containers=False)
  412. time.sleep(1)
  413. def test_git_operation(box_class):
  414. # do not mount workspace, since workspace mount by tests will be owned by root
  415. # while the user_id we get via os.getuid() is different from root
  416. # which causes permission issues
  417. runtime = _load_runtime(
  418. temp_dir=None,
  419. box_class=box_class,
  420. # Need to use non-root user to expose issues
  421. run_as_openhands=True,
  422. )
  423. # this will happen if permission of runtime is not properly configured
  424. # fatal: detected dubious ownership in repository at '/workspace'
  425. # check the ownership of the current directory
  426. action = CmdRunAction(command='ls -alh .')
  427. logger.info(action, extra={'msg_type': 'ACTION'})
  428. obs = runtime.run_action(action)
  429. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  430. assert isinstance(obs, CmdOutputObservation)
  431. assert obs.exit_code == 0
  432. # drwx--S--- 2 openhands root 64 Aug 7 23:32 .
  433. # drwxr-xr-x 1 root root 4.0K Aug 7 23:33 ..
  434. for line in obs.content.split('\r\n'):
  435. if ' ..' in line:
  436. # parent directory should be owned by root
  437. assert 'root' in line
  438. assert 'openhands' not in line
  439. elif ' .' in line:
  440. # current directory should be owned by openhands
  441. # and its group should be root
  442. assert 'openhands' in line
  443. assert 'root' in line
  444. # make sure all git operations are allowed
  445. action = CmdRunAction(command='git init')
  446. logger.info(action, extra={'msg_type': 'ACTION'})
  447. obs = runtime.run_action(action)
  448. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  449. assert isinstance(obs, CmdOutputObservation)
  450. assert obs.exit_code == 0
  451. # create a file
  452. action = CmdRunAction(command='echo "hello" > test_file.txt')
  453. logger.info(action, extra={'msg_type': 'ACTION'})
  454. obs = runtime.run_action(action)
  455. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  456. assert isinstance(obs, CmdOutputObservation)
  457. assert obs.exit_code == 0
  458. # git add
  459. action = CmdRunAction(command='git add test_file.txt')
  460. logger.info(action, extra={'msg_type': 'ACTION'})
  461. obs = runtime.run_action(action)
  462. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  463. assert isinstance(obs, CmdOutputObservation)
  464. assert obs.exit_code == 0
  465. # git diff
  466. action = CmdRunAction(command='git diff')
  467. logger.info(action, extra={'msg_type': 'ACTION'})
  468. obs = runtime.run_action(action)
  469. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  470. assert isinstance(obs, CmdOutputObservation)
  471. assert obs.exit_code == 0
  472. # git commit
  473. action = CmdRunAction(command='git commit -m "test commit"')
  474. logger.info(action, extra={'msg_type': 'ACTION'})
  475. obs = runtime.run_action(action)
  476. logger.info(obs, extra={'msg_type': 'OBSERVATION'})
  477. assert isinstance(obs, CmdOutputObservation)
  478. assert obs.exit_code == 0
  479. runtime.close(rm_all_containers=False)
  480. time.sleep(1)