diff --git a/.github/workflows/ghcr_runtime.yml b/.github/workflows/ghcr_runtime.yml index 0e146bde393c..320a2edb2fe9 100644 --- a/.github/workflows/ghcr_runtime.yml +++ b/.github/workflows/ghcr_runtime.yml @@ -38,10 +38,6 @@ jobs: base_image: - image: 'nikolaik/python-nodejs:python3.11-nodejs22' tag: nikolaik - - image: 'python:3.11-bookworm' - tag: python - - image: 'node:22-bookworm' - tag: node steps: - name: Checkout uses: actions/checkout@v4 @@ -70,31 +66,39 @@ jobs: - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 - - name: Install poetry via pipx - run: pipx install poetry - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - cache: 'poetry' + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pypoetry + ~/.virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + - name: Install poetry via pipx + run: pipx install poetry - name: Install Python dependencies using Poetry run: make install-python-dependencies - name: Create source distribution and Dockerfile run: poetry run python3 openhands/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image.image }} --build_folder containers/runtime --force_rebuild - name: Build and push runtime image ${{ matrix.base_image.image }} - if: "!github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork != true run: | ./containers/build.sh runtime ${{ github.repository_owner }} --push ${{ matrix.base_image.tag }} # Forked repos can't push to GHCR, so we need to upload the image as an artifact - name: Build runtime image ${{ matrix.base_image.image }} for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork uses: docker/build-push-action@v6 with: tags: ghcr.io/all-hands-ai/runtime:${{ github.sha }}-${{ matrix.base_image.tag }} outputs: type=docker,dest=/tmp/runtime-${{ matrix.base_image.tag }}.tar context: containers/runtime - name: Upload runtime image for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork != true uses: actions/upload-artifact@v4 with: name: runtime-${{ matrix.base_image.tag }} @@ -107,7 +111,7 @@ jobs: needs: [ghcr_build_runtime] strategy: matrix: - base_image: ['nikolaik', 'python', 'node'] + base_image: ['nikolaik'] steps: - uses: actions/checkout@v4 - name: Free Disk Space (Ubuntu) @@ -121,22 +125,30 @@ jobs: swap-storage: true # Forked repos can't push to GHCR, so we need to download the image as an artifact - name: Download runtime image for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork uses: actions/download-artifact@v4 with: name: runtime-${{ matrix.base_image }} path: /tmp - name: Load runtime image for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork run: | docker load --input /tmp/runtime-${{ matrix.base_image }}.tar - - name: Install poetry via pipx - run: pipx install poetry + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pypoetry + ~/.virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - cache: 'poetry' + - name: Install poetry via pipx + run: pipx install poetry - name: Install Python dependencies using Poetry run: make install-python-dependencies - name: Run runtime tests @@ -162,27 +174,35 @@ jobs: strategy: fail-fast: false matrix: - base_image: ['nikolaik', 'python', 'node'] + base_image: ['nikolaik'] steps: - uses: actions/checkout@v4 # Forked repos can't push to GHCR, so we need to download the image as an artifact - name: Download runtime image for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork uses: actions/download-artifact@v4 with: name: runtime-${{ matrix.base_image }} path: /tmp - name: Load runtime image for fork - if: "github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork run: | docker load --input /tmp/runtime-${{ matrix.base_image }}.tar - - name: Install poetry via pipx - run: pipx install poetry + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pypoetry + ~/.virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - cache: 'poetry' + - name: Install poetry via pipx + run: pipx install poetry - name: Install Python dependencies using Poetry run: make install-python-dependencies - name: Run integration tests diff --git a/.github/workflows/py-unit-tests.yml b/.github/workflows/py-unit-tests.yml index ec66b9a36b42..dc6691749971 100644 --- a/.github/workflows/py-unit-tests.yml +++ b/.github/workflows/py-unit-tests.yml @@ -22,13 +22,21 @@ jobs: python-version: ['3.11'] steps: - uses: actions/checkout@v4 - - name: Install poetry via pipx - run: pipx install poetry - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: 'poetry' + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pypoetry + ~/.virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + - name: Install poetry via pipx + run: pipx install poetry - name: Install Python dependencies using Poetry run: poetry install --without evaluation,llama-index - name: Install & Start Docker diff --git a/.github/workflows/regenerate_integration_tests.yml b/.github/workflows/regenerate_integration_tests.yml index 01847d5262bc..6cd30df9a241 100644 --- a/.github/workflows/regenerate_integration_tests.yml +++ b/.github/workflows/regenerate_integration_tests.yml @@ -29,18 +29,25 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install poetry via pipx - run: pipx install poetry - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - cache: 'poetry' + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pypoetry + ~/.virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + - name: Install poetry via pipx + run: pipx install poetry - name: Install Python dependencies using Poetry - run: poetry install --without evaluation,llama-index + run: make install-python-dependencies - name: Build Environment run: make build - - name: Regenerate integration tests run: | DEBUG=${{ inputs.debug }} \ @@ -48,7 +55,6 @@ jobs: FORCE_REGENERATE_TESTS=${{ inputs.force_regenerate_tests }} \ FORCE_USE_LLM=${{ inputs.force_use_llm }} \ ./tests/integration/regenerate.sh - - name: Commit changes run: | if git diff --quiet --exit-code; then diff --git a/openhands/runtime/client/runtime.py b/openhands/runtime/client/runtime.py index c87498d8e224..9fe42d8ed77a 100644 --- a/openhands/runtime/client/runtime.py +++ b/openhands/runtime/client/runtime.py @@ -165,7 +165,7 @@ def _init_docker_client() -> docker.DockerClient: return docker.from_env() except Exception as ex: logger.error( - 'Launch docker client failed. Please make sure you have installed docker and started the docker daemon.' + 'Launch docker client failed. Please make sure you have installed docker and started docker desktop/daemon.' ) raise ex diff --git a/tests/runtime/test_env_vars.py b/tests/runtime/test_env_vars.py index 74ba15284f77..ec52397f2040 100644 --- a/tests/runtime/test_env_vars.py +++ b/tests/runtime/test_env_vars.py @@ -34,71 +34,37 @@ def test_env_vars_os_environ(temp_dir, box_class, run_as_openhands): time.sleep(1) -def test_env_vars_runtime_add_env_vars(temp_dir, box_class): +def test_env_vars_runtime_operations(temp_dir, box_class): runtime = _load_runtime(temp_dir, box_class) - runtime.add_env_vars({'QUUX': 'abc"def'}) - obs: CmdOutputObservation = runtime.run_action(CmdRunAction(command='echo $QUUX')) - print(obs) - assert obs.exit_code == 0, 'The exit code should be 0.' + # Test adding single env var + runtime.add_env_vars({'QUUX': 'abc"def'}) + obs = runtime.run_action(CmdRunAction(command='echo $QUUX')) assert ( - obs.content.strip().split('\r\n')[0].strip() == 'abc"def' - ), f'Output: [{obs.content}] for {box_class}' - - runtime.close() - time.sleep(1) - - -def test_env_vars_runtime_add_empty_dict(temp_dir, box_class): - runtime = _load_runtime(temp_dir, box_class) + obs.exit_code == 0 and obs.content.strip().split('\r\n')[0].strip() == 'abc"def' + ) - prev_obs = runtime.run_action(CmdRunAction(command='env')) - assert prev_obs.exit_code == 0, 'The exit code should be 0.' - print(prev_obs) + # Test adding multiple env vars + runtime.add_env_vars({'FOOBAR': 'xyz'}) + obs = runtime.run_action(CmdRunAction(command='echo $QUUX $FOOBAR')) + assert ( + obs.exit_code == 0 + and obs.content.strip().split('\r\n')[0].strip() == 'abc"def xyz' + ) + # Test adding empty dict + prev_env = runtime.run_action(CmdRunAction(command='env')).content runtime.add_env_vars({}) + current_env = runtime.run_action(CmdRunAction(command='env')).content + assert prev_env == current_env - obs = runtime.run_action(CmdRunAction(command='env')) - assert obs.exit_code == 0, 'The exit code should be 0.' - print(obs) + # Test overwriting env vars + runtime.add_env_vars({'QUUX': 'new_value'}) + obs = runtime.run_action(CmdRunAction(command='echo $QUUX')) assert ( - obs.content == prev_obs.content - ), 'The env var content should be the same after adding an empty dict.' - - runtime.close() - time.sleep(1) - - -def test_env_vars_runtime_add_multiple_env_vars(temp_dir, box_class): - runtime = _load_runtime(temp_dir, box_class) - runtime.add_env_vars({'QUUX': 'abc"def', 'FOOBAR': 'xyz'}) - - obs: CmdOutputObservation = runtime.run_action( - CmdRunAction(command='echo $QUUX $FOOBAR') + obs.exit_code == 0 + and obs.content.strip().split('\r\n')[0].strip() == 'new_value' ) - print(obs) - assert obs.exit_code == 0, 'The exit code should be 0.' - assert ( - obs.content.strip().split('\r\n')[0].strip() == 'abc"def xyz' - ), f'Output: [{obs.content}] for {box_class}' runtime.close() time.sleep(1) - - -def test_env_vars_runtime_add_env_vars_overwrite(temp_dir, box_class): - with patch.dict(os.environ, {'SANDBOX_ENV_FOOBAR': 'BAZ'}): - runtime = _load_runtime(temp_dir, box_class) - runtime.add_env_vars({'FOOBAR': 'xyz'}) - - obs: CmdOutputObservation = runtime.run_action( - CmdRunAction(command='echo $FOOBAR') - ) - print(obs) - assert obs.exit_code == 0, 'The exit code should be 0.' - assert ( - obs.content.strip().split('\r\n')[0].strip() == 'xyz' - ), f'Output: [{obs.content}] for {box_class}' - - runtime.close() - time.sleep(1)