Просмотр исходного кода

workspace_mount_path sentinel: an undefined string (#2431)

Engel Nyst 1 год назад
Родитель
Сommit
1cc70be616
2 измененных файлов с 61 добавлено и 7 удалено
  1. 9 2
      opendevin/core/config.py
  2. 52 5
      tests/unit/test_config.py

+ 9 - 2
opendevin/core/config.py

@@ -5,6 +5,7 @@ import pathlib
 import platform
 import uuid
 from dataclasses import dataclass, field, fields, is_dataclass
+from enum import Enum
 from types import UnionType
 from typing import Any, ClassVar, get_args, get_origin
 
@@ -122,6 +123,10 @@ class AgentConfig(metaclass=Singleton):
         return dict
 
 
+class UndefinedString(str, Enum):
+    UNDEFINED = 'UNDEFINED'
+
+
 @dataclass
 class AppConfig(metaclass=Singleton):
     """
@@ -159,7 +164,9 @@ class AppConfig(metaclass=Singleton):
     file_store: str = 'memory'
     file_store_path: str = '/tmp/file_store'
     workspace_base: str = os.path.join(os.getcwd(), 'workspace')
-    workspace_mount_path: str | None = None
+    workspace_mount_path: str = (
+        UndefinedString.UNDEFINED  # this path should always be set when config is fully loaded
+    )
     workspace_mount_path_in_sandbox: str = '/workspace'
     workspace_mount_rewrite: str | None = None
     cache_dir: str = '/tmp/cache'
@@ -374,7 +381,7 @@ def finalize_config(config: AppConfig):
     """
 
     # Set workspace_mount_path if not set by the user
-    if config.workspace_mount_path is None:
+    if config.workspace_mount_path is UndefinedString.UNDEFINED:
         config.workspace_mount_path = os.path.abspath(config.workspace_base)
     config.workspace_base = os.path.abspath(config.workspace_base)
 

+ 52 - 5
tests/unit/test_config.py

@@ -6,6 +6,7 @@ from opendevin.core.config import (
     AgentConfig,
     AppConfig,
     LLMConfig,
+    UndefinedString,
     finalize_config,
     load_from_env,
     load_from_toml,
@@ -76,6 +77,12 @@ def test_load_from_old_style_env(monkeypatch, default_config):
     assert default_config.agent.memory_enabled is True
     assert default_config.agent.name == 'PlannerAgent'
     assert default_config.workspace_base == '/opt/files/workspace'
+    assert (
+        default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    )  # before finalize_config
+    assert (
+        default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
+    )
 
 
 def test_load_from_new_style_toml(default_config, temp_toml_file):
@@ -102,6 +109,19 @@ workspace_base = "/opt/files2/workspace"
     assert default_config.agent.memory_enabled is True
     assert default_config.workspace_base == '/opt/files2/workspace'
 
+    # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
+    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert (
+        default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
+    )
+    assert default_config.workspace_mount_path_in_sandbox == '/workspace'
+
+    finalize_config(default_config)
+
+    # after finalize_config, workspace_mount_path is set to the absolute path of workspace_base
+    # if it was undefined
+    assert default_config.workspace_mount_path == '/opt/files2/workspace'
+
 
 def test_env_overrides_toml(monkeypatch, default_config, temp_toml_file):
     # Test that environment variables override TOML values using monkeypatch
@@ -118,23 +138,42 @@ disable_color = true
 """)
 
     monkeypatch.setenv('LLM_API_KEY', 'env-api-key')
-    monkeypatch.setenv('WORKSPACE_BASE', '/opt/files4/workspace')
+    monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED')
     monkeypatch.setenv('SANDBOX_TYPE', 'ssh')
 
     load_from_toml(default_config, temp_toml_file)
+
+    # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
+    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+
     load_from_env(default_config, os.environ)
 
     assert os.environ.get('LLM_MODEL') is None
     assert default_config.llm.model == 'test-model'
     assert default_config.llm.api_key == 'env-api-key'
-    assert default_config.workspace_base == '/opt/files4/workspace'
+
+    # after we set workspace_base to 'UNDEFINED' in the environment,
+    # workspace_base should be set to that
+    # workspace_mount path is still UndefinedString.UNDEFINED
+    assert default_config.workspace_base is not UndefinedString.UNDEFINED
+    assert default_config.workspace_base == 'UNDEFINED'
+    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert default_config.workspace_mount_path == 'UNDEFINED'
+
     assert default_config.sandbox_type == 'ssh'
     assert default_config.disable_color is True
 
+    finalize_config(default_config)
+    # after finalize_config, workspace_mount_path is set to absolute path of workspace_base if it was undefined
+    assert default_config.workspace_mount_path == os.getcwd() + '/UNDEFINED'
+
 
 def test_defaults_dict_after_updates(default_config):
     # Test that `defaults_dict` retains initial values after updates.
     initial_defaults = default_config.defaults_dict
+    assert (
+        initial_defaults['workspace_mount_path']['default'] is UndefinedString.UNDEFINED
+    )
     updated_config = AppConfig()
     updated_config.llm.api_key = 'updated-api-key'
     updated_config.agent.name = 'MonologueAgent'
@@ -142,14 +181,17 @@ def test_defaults_dict_after_updates(default_config):
     defaults_after_updates = updated_config.defaults_dict
     assert defaults_after_updates['llm']['api_key']['default'] is None
     assert defaults_after_updates['agent']['name']['default'] == 'CodeActAgent'
+    assert (
+        defaults_after_updates['workspace_mount_path']['default']
+        is UndefinedString.UNDEFINED
+    )
     assert defaults_after_updates == initial_defaults
 
-    AppConfig.reset()
-
 
 def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
     # Invalid TOML format doesn't break the configuration
     monkeypatch.setenv('LLM_MODEL', 'gpt-5-turbo-1106')
+    monkeypatch.setenv('WORKSPACE_MOUNT_PATH', '/home/user/project')
     monkeypatch.delenv('LLM_API_KEY', raising=False)
     with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
         toml_file.write('INVALID TOML CONTENT')
@@ -162,10 +204,12 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
     assert default_config.llm.custom_llm_provider is None
     if default_config.llm.api_key is not None:  # prevent leak
         pytest.fail('LLM API key should be empty.')
+    assert default_config.workspace_mount_path == '/home/user/project'
 
 
 def test_finalize_config(default_config):
     # Test finalize config
+    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
     default_config.sandbox_type = 'local'
     finalize_config(default_config)
 
@@ -173,11 +217,14 @@ def test_finalize_config(default_config):
         default_config.workspace_mount_path_in_sandbox
         == default_config.workspace_mount_path
     )
+    assert default_config.workspace_mount_path == os.path.abspath(
+        default_config.workspace_base
+    )
 
 
 # tests for workspace, mount path, path in sandbox, cache dir
 def test_workspace_mount_path_default(default_config):
-    assert default_config.workspace_mount_path is None
+    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
     finalize_config(default_config)
     assert default_config.workspace_mount_path == os.path.abspath(
         default_config.workspace_base