55 |
The Ultimate Answer
56 |
Click the button to reveal the answer to life, the universe, and everything.
57 |
58 |
59 |
60 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/tests/integration/workspace/test_edits/bad.txt:
--------------------------------------------------------------------------------
1 | This is a stupid typoo.
2 | Really?
3 | No mor typos!
4 | Enjoy!
5 |
--------------------------------------------------------------------------------
/tests/test_fileops.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from opendevin.core.config import config
6 | from opendevin.runtime.server import files
7 |
8 | SANDBOX_PATH_PREFIX = '/workspace'
9 |
10 |
11 | def test_resolve_path():
12 | assert (
13 | files.resolve_path('test.txt', '/workspace')
14 | == Path(config.workspace_base) / 'test.txt'
15 | )
16 | assert (
17 | files.resolve_path('subdir/test.txt', '/workspace')
18 | == Path(config.workspace_base) / 'subdir' / 'test.txt'
19 | )
20 | assert (
21 | files.resolve_path(Path(SANDBOX_PATH_PREFIX) / 'test.txt', '/workspace')
22 | == Path(config.workspace_base) / 'test.txt'
23 | )
24 | assert (
25 | files.resolve_path(
26 | Path(SANDBOX_PATH_PREFIX) / 'subdir' / 'test.txt', '/workspace'
27 | )
28 | == Path(config.workspace_base) / 'subdir' / 'test.txt'
29 | )
30 | assert (
31 | files.resolve_path(
32 | Path(SANDBOX_PATH_PREFIX) / 'subdir' / '..' / 'test.txt', '/workspace'
33 | )
34 | == Path(config.workspace_base) / 'test.txt'
35 | )
36 | with pytest.raises(PermissionError):
37 | files.resolve_path(Path(SANDBOX_PATH_PREFIX) / '..' / 'test.txt', '/workspace')
38 | with pytest.raises(PermissionError):
39 | files.resolve_path(Path('..') / 'test.txt', '/workspace')
40 | with pytest.raises(PermissionError):
41 | files.resolve_path(Path('/') / 'test.txt', '/workspace')
42 | assert (
43 | files.resolve_path('test.txt', '/workspace/test')
44 | == Path(config.workspace_base) / 'test' / 'test.txt'
45 | )
46 |
--------------------------------------------------------------------------------
/tests/unit/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | This folder contains unit tests that could be run locally.
4 |
5 | Run all test:
6 |
7 | ```bash
8 | poetry run pytest ./tests/unit
9 | ```
10 |
11 | Run specific test file:
12 |
13 | ```bash
14 | poetry run pytest ./tests/unit/test_micro_agents.py
15 | ```
16 |
17 | Run specific unit test
18 |
19 | ```bash
20 | poetry run pytest ./tests/unit/test_micro_agents.py::test_coder_agent_with_summary
21 | ```
22 |
23 | For a more verbose output, to above calls the `-v` flag can be used (even more verbose: `-vv` and `-vvv`):
24 |
25 | ```bash
26 | poetry run pytest -v ./tests/unit/test_micro_agents.py
27 | ```
28 |
29 | More details see [pytest doc](https://docs.pytest.org/en/latest/contents.html)
30 |
--------------------------------------------------------------------------------
/tests/unit/test_arg_parser.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from opendevin.core.config import get_parser
4 |
5 |
6 | def test_help_message(capsys):
7 | parser = get_parser()
8 | with pytest.raises(SystemExit): # `--help` causes SystemExit
9 | parser.parse_args(['--help'])
10 | captured = capsys.readouterr()
11 | expected_help_message = """
12 | usage: pytest [-h] [-d DIRECTORY] [-t TASK] [-f FILE] [-c AGENT_CLS]
13 | [-m MODEL_NAME] [-i MAX_ITERATIONS] [-b MAX_BUDGET_PER_TASK]
14 | [-n MAX_CHARS] [--eval-output-dir EVAL_OUTPUT_DIR]
15 | [--eval-n-limit EVAL_N_LIMIT]
16 | [--eval-num-workers EVAL_NUM_WORKERS] [--eval-note EVAL_NOTE]
17 | [-l LLM_CONFIG]
18 |
19 | Run an agent with a specific task
20 |
21 | options:
22 | -h, --help show this help message and exit
23 | -d DIRECTORY, --directory DIRECTORY
24 | The working directory for the agent
25 | -t TASK, --task TASK The task for the agent to perform
26 | -f FILE, --file FILE Path to a file containing the task. Overrides -t if
27 | both are provided.
28 | -c AGENT_CLS, --agent-cls AGENT_CLS
29 | The agent class to use
30 | -m MODEL_NAME, --model-name MODEL_NAME
31 | The (litellm) model name to use
32 | -i MAX_ITERATIONS, --max-iterations MAX_ITERATIONS
33 | The maximum number of iterations to run the agent
34 | -b MAX_BUDGET_PER_TASK, --max-budget-per-task MAX_BUDGET_PER_TASK
35 | The maximum budget allowed per task, beyond which the
36 | agent will stop.
37 | -n MAX_CHARS, --max-chars MAX_CHARS
38 | The maximum number of characters to send to and
39 | receive from LLM per task
40 | --eval-output-dir EVAL_OUTPUT_DIR
41 | The directory to save evaluation output
42 | --eval-n-limit EVAL_N_LIMIT
43 | The number of instances to evaluate
44 | --eval-num-workers EVAL_NUM_WORKERS
45 | The number of workers to use for evaluation
46 | --eval-note EVAL_NOTE
47 | The note to add to the evaluation directory
48 | -l LLM_CONFIG, --llm-config LLM_CONFIG
49 | The group of llm settings, e.g. a [llama3] section in
50 | the toml file. Overrides model if both are provided.
51 | """
52 |
53 | actual_lines = captured.out.strip().split('\n')
54 | print('\n'.join(actual_lines))
55 | expected_lines = expected_help_message.strip().split('\n')
56 |
57 | # Ensure both outputs have the same number of lines
58 | assert len(actual_lines) == len(
59 | expected_lines
60 | ), 'The number of lines in the help message does not match.'
61 |
62 | # Compare each line
63 | for actual, expected in zip(actual_lines, expected_lines):
64 | assert (
65 | actual.strip() == expected.strip()
66 | ), f"Expected '{expected}', got '{actual}'"
67 |
--------------------------------------------------------------------------------
/tests/unit/test_event_stream.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 |
5 | from opendevin.events import EventSource, EventStream
6 | from opendevin.events.action import NullAction
7 | from opendevin.events.observation import NullObservation
8 |
9 |
10 | def collect_events(stream):
11 | return [event for event in stream.get_events()]
12 |
13 |
14 | @pytest.mark.asyncio
15 | async def test_basic_flow():
16 | stream = EventStream('abc')
17 | await stream.add_event(NullAction(), EventSource.AGENT)
18 | assert len(collect_events(stream)) == 1
19 |
20 |
21 | @pytest.mark.asyncio
22 | async def test_stream_storage():
23 | stream = EventStream('def')
24 | await stream.add_event(NullObservation(''), EventSource.AGENT)
25 | assert len(collect_events(stream)) == 1
26 | content = stream._file_store.read('sessions/def/events/0.json')
27 | assert content is not None
28 | data = json.loads(content)
29 | assert 'timestamp' in data
30 | del data['timestamp']
31 | assert data == {
32 | 'id': 0,
33 | 'source': 'agent',
34 | 'observation': 'null',
35 | 'content': '',
36 | 'extras': {},
37 | 'message': 'No observation',
38 | }
39 |
40 |
41 | @pytest.mark.asyncio
42 | async def test_rehydration():
43 | stream1 = EventStream('es1')
44 | await stream1.add_event(NullObservation('obs1'), EventSource.AGENT)
45 | await stream1.add_event(NullObservation('obs2'), EventSource.AGENT)
46 | assert len(collect_events(stream1)) == 2
47 |
48 | stream2 = EventStream('es2')
49 | assert len(collect_events(stream2)) == 0
50 |
51 | stream1rehydrated = EventStream('es1')
52 | events = collect_events(stream1rehydrated)
53 | assert len(events) == 2
54 | assert events[0].content == 'obs1'
55 | assert events[1].content == 'obs2'
56 |
--------------------------------------------------------------------------------
/tests/unit/test_json.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from opendevin.core.utils import json
4 | from opendevin.events.action import MessageAction
5 |
6 |
7 | def test_event_serialization_deserialization():
8 | message = MessageAction(content='This is a test.', wait_for_response=False)
9 | message._id = 42
10 | message._timestamp = datetime(2020, 1, 1, 0, 0, 0)
11 | serialized = json.dumps(message)
12 | deserialized = json.loads(serialized)
13 | expected = {
14 | 'id': 42,
15 | 'timestamp': '2020-01-01T00:00:00',
16 | 'action': 'message',
17 | 'message': 'This is a test.',
18 | 'args': {
19 | 'content': 'This is a test.',
20 | 'wait_for_response': False,
21 | },
22 | }
23 | assert deserialized == expected
24 |
25 |
26 | def test_array_serialization_deserialization():
27 | message = MessageAction(content='This is a test.', wait_for_response=False)
28 | message._id = 42
29 | message._timestamp = datetime(2020, 1, 1, 0, 0, 0)
30 | serialized = json.dumps([message])
31 | deserialized = json.loads(serialized)
32 | expected = [
33 | {
34 | 'id': 42,
35 | 'timestamp': '2020-01-01T00:00:00',
36 | 'action': 'message',
37 | 'message': 'This is a test.',
38 | 'args': {
39 | 'content': 'This is a test.',
40 | 'wait_for_response': False,
41 | },
42 | }
43 | ]
44 | assert deserialized == expected
45 |
--------------------------------------------------------------------------------
/tests/unit/test_observation_serialization.py:
--------------------------------------------------------------------------------
1 | from opendevin.events.observation import (
2 | CmdOutputObservation,
3 | Observation,
4 | )
5 | from opendevin.events.serialization import (
6 | event_from_dict,
7 | event_to_dict,
8 | event_to_memory,
9 | )
10 |
11 |
12 | def serialization_deserialization(original_observation_dict, cls):
13 | observation_instance = event_from_dict(original_observation_dict)
14 | assert isinstance(
15 | observation_instance, Observation
16 | ), 'The observation instance should be an instance of Action.'
17 | assert isinstance(
18 | observation_instance, cls
19 | ), 'The observation instance should be an instance of CmdOutputObservation.'
20 | serialized_observation_dict = event_to_dict(observation_instance)
21 | serialized_observation_memory = event_to_memory(observation_instance)
22 | assert (
23 | serialized_observation_dict == original_observation_dict
24 | ), 'The serialized observation should match the original observation dict.'
25 | original_observation_dict.pop('message', None)
26 | original_observation_dict.pop('id', None)
27 | original_observation_dict.pop('timestamp', None)
28 | assert (
29 | serialized_observation_memory == original_observation_dict
30 | ), 'The serialized observation memory should match the original observation dict.'
31 |
32 |
33 | # Additional tests for various observation subclasses can be included here
34 | def test_observation_event_props_serialization_deserialization():
35 | original_observation_dict = {
36 | 'id': 42,
37 | 'source': 'agent',
38 | 'timestamp': '2021-08-01T12:00:00',
39 | 'observation': 'run',
40 | 'message': 'Command `ls -l` executed with exit code 0.',
41 | 'extras': {'exit_code': 0, 'command': 'ls -l', 'command_id': 3},
42 | 'content': 'foo.txt',
43 | }
44 | serialization_deserialization(original_observation_dict, CmdOutputObservation)
45 |
46 |
47 | def test_command_output_observation_serialization_deserialization():
48 | original_observation_dict = {
49 | 'observation': 'run',
50 | 'extras': {'exit_code': 0, 'command': 'ls -l', 'command_id': 3},
51 | 'message': 'Command `ls -l` executed with exit code 0.',
52 | 'content': 'foo.txt',
53 | }
54 | serialization_deserialization(original_observation_dict, CmdOutputObservation)
55 |
--------------------------------------------------------------------------------
/tests/unit/test_storage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 | import pytest
5 |
6 | from opendevin.storage.local import LocalFileStore
7 | from opendevin.storage.memory import InMemoryFileStore
8 |
9 |
10 | @pytest.fixture
11 | def setup_env():
12 | os.makedirs('./_test_files_tmp', exist_ok=True)
13 |
14 | yield
15 |
16 | shutil.rmtree('./_test_files_tmp')
17 |
18 |
19 | def test_basic_fileops(setup_env):
20 | filename = 'test.txt'
21 | for store in [LocalFileStore('./_test_files_tmp'), InMemoryFileStore()]:
22 | store.write(filename, 'Hello, world!')
23 | assert store.read(filename) == 'Hello, world!'
24 | assert store.list('') == [filename]
25 | store.delete(filename)
26 | with pytest.raises(FileNotFoundError):
27 | store.read(filename)
28 |
29 |
30 | def test_complex_path_fileops(setup_env):
31 | filenames = ['foo.bar.baz', './foo/bar/baz', 'foo/bar/baz', '/foo/bar/baz']
32 | for store in [LocalFileStore('./_test_files_tmp'), InMemoryFileStore()]:
33 | for filename in filenames:
34 | store.write(filename, 'Hello, world!')
35 | assert store.read(filename) == 'Hello, world!'
36 | store.delete(filename)
37 | with pytest.raises(FileNotFoundError):
38 | store.read(filename)
39 |
40 |
41 | def test_list(setup_env):
42 | for store in [LocalFileStore('./_test_files_tmp'), InMemoryFileStore()]:
43 | store.write('foo.txt', 'Hello, world!')
44 | store.write('bar.txt', 'Hello, world!')
45 | store.write('baz.txt', 'Hello, world!')
46 | assert store.list('').sort() == ['foo.txt', 'bar.txt', 'baz.txt'].sort()
47 | store.delete('foo.txt')
48 | store.delete('bar.txt')
49 | store.delete('baz.txt')
50 |
51 |
52 | def test_deep_list(setup_env):
53 | for store in [LocalFileStore('./_test_files_tmp'), InMemoryFileStore()]:
54 | store.write('foo/bar/baz.txt', 'Hello, world!')
55 | store.write('foo/bar/qux.txt', 'Hello, world!')
56 | store.write('foo/bar/quux.txt', 'Hello, world!')
57 | assert store.list('') == ['foo/'], f'for class {store.__class__}'
58 | assert store.list('foo') == ['foo/bar/']
59 | assert (
60 | store.list('foo/bar').sort()
61 | == ['foo/bar/baz.txt', 'foo/bar/qux.txt', 'foo/bar/quux.txt'].sort()
62 | )
63 | store.delete('foo/bar/baz.txt')
64 | store.delete('foo/bar/qux.txt')
65 | store.delete('foo/bar/quux.txt')
66 |
--------------------------------------------------------------------------------