test_runtime_build.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import os
  2. import tarfile
  3. import tempfile
  4. from importlib.metadata import version
  5. from unittest.mock import MagicMock, patch
  6. import pytest
  7. import toml
  8. from opendevin.runtime.utils.runtime_build import (
  9. _generate_dockerfile,
  10. _get_package_version,
  11. _put_source_code_to_dir,
  12. build_runtime_image,
  13. get_new_image_name,
  14. )
  15. OD_VERSION = f'od_v{_get_package_version()}'
  16. RUNTIME_IMAGE_PREFIX = 'od_runtime'
  17. @pytest.fixture
  18. def temp_dir():
  19. with tempfile.TemporaryDirectory() as temp_dir:
  20. yield temp_dir
  21. def test_put_source_code_to_dir(temp_dir):
  22. folder_name = _put_source_code_to_dir(temp_dir)
  23. # assert there is a file called 'project.tar.gz' in the temp_dir
  24. assert os.path.exists(os.path.join(temp_dir, 'project.tar.gz'))
  25. # untar the file
  26. with tarfile.open(os.path.join(temp_dir, 'project.tar.gz'), 'r:gz') as tar:
  27. tar.extractall(path=temp_dir)
  28. # check the source file is the same as the current code base
  29. assert os.path.exists(os.path.join(temp_dir, folder_name, 'pyproject.toml'))
  30. # make sure the version from the pyproject.toml is the same as the current version
  31. with open(os.path.join(temp_dir, folder_name, 'pyproject.toml'), 'r') as f:
  32. pyproject = toml.load(f)
  33. _pyproject_version = pyproject['tool']['poetry']['version']
  34. assert _pyproject_version == version('opendevin')
  35. def test_generate_dockerfile_scratch():
  36. base_image = 'debian:11'
  37. source_code_dirname = 'dummy'
  38. dockerfile_content = _generate_dockerfile(
  39. base_image,
  40. source_code_dirname=source_code_dirname,
  41. skip_init=False,
  42. )
  43. assert base_image in dockerfile_content
  44. assert 'apt-get update' in dockerfile_content
  45. assert 'apt-get install -y wget sudo apt-utils' in dockerfile_content
  46. assert (
  47. 'RUN /opendevin/miniforge3/bin/mamba install conda-forge::poetry -y'
  48. in dockerfile_content
  49. )
  50. # Check the update command
  51. assert f'mv /opendevin/{source_code_dirname} /opendevin/code' in dockerfile_content
  52. assert (
  53. '/opendevin/miniforge3/bin/mamba run -n base poetry install'
  54. in dockerfile_content
  55. )
  56. def test_generate_dockerfile_skip_init():
  57. base_image = 'debian:11'
  58. source_code_dirname = 'dummy'
  59. dockerfile_content = _generate_dockerfile(
  60. base_image,
  61. source_code_dirname=source_code_dirname,
  62. skip_init=True,
  63. )
  64. # These commands SHOULD NOT include in the dockerfile if skip_init is True
  65. assert 'RUN apt update && apt install -y wget sudo' not in dockerfile_content
  66. assert (
  67. 'RUN /opendevin/miniforge3/bin/mamba install conda-forge::poetry -y'
  68. not in dockerfile_content
  69. )
  70. # These update commands SHOULD still in the dockerfile
  71. assert (
  72. f'RUN mv /opendevin/{source_code_dirname} /opendevin/code' in dockerfile_content
  73. )
  74. assert (
  75. '/opendevin/miniforge3/bin/mamba run -n base poetry install'
  76. in dockerfile_content
  77. )
  78. def test_get_new_image_name_eventstream():
  79. base_image = 'debian:11'
  80. new_image_name = get_new_image_name(base_image)
  81. assert new_image_name == f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'
  82. base_image = 'ubuntu:22.04'
  83. new_image_name = get_new_image_name(base_image)
  84. assert (
  85. new_image_name == f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_ubuntu_tag_22.04'
  86. )
  87. base_image = 'ubuntu'
  88. new_image_name = get_new_image_name(base_image)
  89. assert (
  90. new_image_name == f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_ubuntu_tag_latest'
  91. )
  92. def test_get_new_image_name_eventstream_dev_mode():
  93. base_image = f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'
  94. new_image_name = get_new_image_name(base_image, dev_mode=True)
  95. assert (
  96. new_image_name == f'{RUNTIME_IMAGE_PREFIX}_dev:{OD_VERSION}_image_debian_tag_11'
  97. )
  98. base_image = f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_ubuntu_tag_22.04'
  99. new_image_name = get_new_image_name(base_image, dev_mode=True)
  100. assert (
  101. new_image_name
  102. == f'{RUNTIME_IMAGE_PREFIX}_dev:{OD_VERSION}_image_ubuntu_tag_22.04'
  103. )
  104. base_image = f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_ubuntu_tag_latest'
  105. new_image_name = get_new_image_name(base_image, dev_mode=True)
  106. assert (
  107. new_image_name
  108. == f'{RUNTIME_IMAGE_PREFIX}_dev:{OD_VERSION}_image_ubuntu_tag_latest'
  109. )
  110. def test_get_new_image_name_eventstream_dev_invalid_base_image():
  111. with pytest.raises(ValueError):
  112. base_image = 'debian:11'
  113. get_new_image_name(base_image, dev_mode=True)
  114. with pytest.raises(ValueError):
  115. base_image = 'ubuntu:22.04'
  116. get_new_image_name(base_image, dev_mode=True)
  117. with pytest.raises(ValueError):
  118. base_image = 'ubuntu:latest'
  119. get_new_image_name(base_image, dev_mode=True)
  120. @patch('opendevin.runtime.utils.runtime_build._build_sandbox_image')
  121. @patch('opendevin.runtime.utils.runtime_build.docker.DockerClient')
  122. def test_build_runtime_image_from_scratch(mock_docker_client, mock_build_sandbox_image):
  123. base_image = 'debian:11'
  124. mock_docker_client.images.list.return_value = []
  125. image_name = build_runtime_image(base_image, mock_docker_client)
  126. assert image_name == f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'
  127. mock_build_sandbox_image.assert_called_once_with(
  128. base_image,
  129. f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11',
  130. mock_docker_client,
  131. skip_init=False,
  132. )
  133. @patch('opendevin.runtime.utils.runtime_build._build_sandbox_image')
  134. @patch('opendevin.runtime.utils.runtime_build.docker.DockerClient')
  135. def test_build_runtime_image_exist_no_update_source(
  136. mock_docker_client, mock_build_sandbox_image
  137. ):
  138. base_image = 'debian:11'
  139. mock_docker_client.images.list.return_value = [
  140. MagicMock(tags=[f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'])
  141. ]
  142. image_name = build_runtime_image(base_image, mock_docker_client)
  143. assert image_name == f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'
  144. mock_build_sandbox_image.assert_not_called()
  145. @patch('opendevin.runtime.utils.runtime_build._build_sandbox_image')
  146. @patch('opendevin.runtime.utils.runtime_build.docker.DockerClient')
  147. def test_build_runtime_image_exist_with_update_source(
  148. mock_docker_client, mock_build_sandbox_image
  149. ):
  150. base_image = 'debian:11'
  151. mock_docker_client.images.list.return_value = [
  152. MagicMock(tags=[f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11'])
  153. ]
  154. image_name = build_runtime_image(
  155. base_image, mock_docker_client, update_source_code=True
  156. )
  157. assert image_name == f'{RUNTIME_IMAGE_PREFIX}_dev:{OD_VERSION}_image_debian_tag_11'
  158. mock_build_sandbox_image.assert_called_once_with(
  159. f'{RUNTIME_IMAGE_PREFIX}:{OD_VERSION}_image_debian_tag_11',
  160. f'{RUNTIME_IMAGE_PREFIX}_dev:{OD_VERSION}_image_debian_tag_11',
  161. mock_docker_client,
  162. skip_init=True,
  163. )