test_runtime_build.py 6.9 KB

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