conftest.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import os
  2. import random
  3. import time
  4. import pytest
  5. from pytest import TempPathFactory
  6. from openhands.core.config import AppConfig, SandboxConfig, load_from_env
  7. from openhands.events import EventStream
  8. from openhands.runtime.client.runtime import EventStreamRuntime
  9. from openhands.runtime.plugins import AgentSkillsRequirement, JupyterRequirement
  10. from openhands.runtime.remote.runtime import RemoteRuntime
  11. from openhands.runtime.runtime import Runtime
  12. from openhands.storage import get_file_store
  13. @pytest.fixture(autouse=True)
  14. def print_method_name(request):
  15. print('\n########################################################################')
  16. print(f'Running test: {request.node.name}')
  17. print('########################################################################')
  18. yield
  19. @pytest.fixture
  20. def temp_dir(tmp_path_factory: TempPathFactory) -> str:
  21. """
  22. Creates a unique temporary directory
  23. Parameters:
  24. - tmp_path_factory (TempPathFactory): A TempPathFactory class
  25. Returns:
  26. - str: The temporary directory path that was created
  27. """
  28. unique_suffix = random.randint(10000, 99999)
  29. temp_directory = tmp_path_factory.mktemp(
  30. f'test_runtime_{unique_suffix}', numbered=False
  31. )
  32. return str(temp_directory)
  33. TEST_RUNTIME = os.getenv('TEST_RUNTIME', 'eventstream')
  34. # Depending on TEST_RUNTIME, feed the appropriate box class(es) to the test.
  35. def get_box_classes():
  36. runtime = TEST_RUNTIME
  37. if runtime.lower() == 'eventstream':
  38. return [EventStreamRuntime]
  39. elif runtime.lower() == 'remote':
  40. return [RemoteRuntime]
  41. else:
  42. raise ValueError(f'Invalid runtime: {runtime}')
  43. # This assures that all tests run together per runtime, not alternating between them,
  44. # which cause errors (especially outside GitHub actions).
  45. @pytest.fixture(scope='module', params=get_box_classes())
  46. def box_class(request):
  47. time.sleep(2)
  48. return request.param
  49. # TODO: We will change this to `run_as_user` when `ServerRuntime` is deprecated.
  50. # since `EventStreamRuntime` supports running as an arbitrary user.
  51. @pytest.fixture(scope='module', params=[True, False])
  52. def run_as_openhands(request):
  53. time.sleep(1)
  54. return request.param
  55. @pytest.fixture(scope='module', params=[True, False])
  56. def enable_auto_lint(request):
  57. time.sleep(1)
  58. return request.param
  59. @pytest.fixture(scope='module', params=None)
  60. def base_container_image(request):
  61. time.sleep(1)
  62. env_image = os.environ.get('SANDBOX_BASE_CONTAINER_IMAGE')
  63. if env_image:
  64. request.param = env_image
  65. else:
  66. if not hasattr(request, 'param'): # prevent runtime AttributeError
  67. request.param = None
  68. if request.param is None and hasattr(request.config, 'sandbox'):
  69. try:
  70. request.param = request.config.sandbox.getoption(
  71. '--base_container_image'
  72. )
  73. except ValueError:
  74. request.param = None
  75. if request.param is None:
  76. request.param = pytest.param(
  77. 'nikolaik/python-nodejs:python3.11-nodejs22',
  78. 'python:3.11-bookworm',
  79. 'node:22-bookworm',
  80. 'golang:1.23-bookworm',
  81. )
  82. print(f'Container image: {request.param}')
  83. return request.param
  84. @pytest.fixture
  85. def runtime(temp_dir, box_class, run_as_openhands):
  86. runtime = _load_runtime(temp_dir, box_class, run_as_openhands)
  87. yield runtime
  88. time.sleep(1)
  89. def _load_runtime(
  90. temp_dir,
  91. box_class,
  92. run_as_openhands: bool = True,
  93. enable_auto_lint: bool = False,
  94. base_container_image: str | None = None,
  95. browsergym_eval_env: str | None = None,
  96. ) -> Runtime:
  97. sid = 'test'
  98. cli_session = 'main_test'
  99. # AgentSkills need to be initialized **before** Jupyter
  100. # otherwise Jupyter will not access the proper dependencies installed by AgentSkills
  101. plugins = [AgentSkillsRequirement(), JupyterRequirement()]
  102. config = AppConfig(
  103. workspace_base=temp_dir,
  104. workspace_mount_path=temp_dir,
  105. sandbox=SandboxConfig(
  106. use_host_network=True,
  107. browsergym_eval_env=browsergym_eval_env,
  108. ),
  109. )
  110. load_from_env(config, os.environ)
  111. config.run_as_openhands = run_as_openhands
  112. config.sandbox.enable_auto_lint = enable_auto_lint
  113. if base_container_image is not None:
  114. config.sandbox.base_container_image = base_container_image
  115. file_store = get_file_store(config.file_store, config.file_store_path)
  116. event_stream = EventStream(cli_session, file_store)
  117. runtime = box_class(
  118. config=config,
  119. event_stream=event_stream,
  120. sid=sid,
  121. plugins=plugins,
  122. )
  123. time.sleep(1)
  124. return runtime
  125. # Export necessary function
  126. __all__ = ['_load_runtime']