import traceback from types import SimpleNamespace from typing import List, Optional from uuid import uuid4 import pytest import linecache from e2b import Template, CopyItem, wait_for_timeout from e2b.template.types import TemplateBuildStatus import e2b.template_sync.main as template_sync_main import e2b.template_sync.build_api as build_api_mod non_existent_path = "nonexistent/path" # map template alias -> failed step index failure_map: dict[str, Optional[int]] = { "from_image": 0, "from_template": 0, "from_dockerfile": 0, "from_image_registry": 0, "from_aws_registry": 0, "from_gcp_registry": 0, "copy": None, "copy_items": None, # multi-source copy produces two COPY instructions (steps 1 and 2), # the run_cmd after it is step 3 "multi_source_copy_second_source": 2, "multi_source_copy_next_step": 3, "copy_items_second_item": 2, "copy_items_next_step": 3, "remove": 1, "rename": 1, "make_dir": 1, "make_symlink": 1, "run_cmd": 1, "set_workdir": 1, "set_user": 1, "pip_install": 1, "npm_install": 1, "apt_install": 1, "git_clone": 1, "set_start_cmd": 1, "add_mcp_server": None, "beta_dev_container_prebuild": 1, "beta_set_dev_container_start": 1, } @pytest.fixture(autouse=True) def mock_template_build(monkeypatch): def mock_request_build( client, name: str, tags: Optional[List[str]], cpu_count: int, memory_mb: int ): return SimpleNamespace(template_id=name, build_id=str(uuid4()), tags=tags or []) def mock_trigger_build(client, template_id: str, build_id: str, template): return None def mock_get_file_upload_link( client, template_id: str, files_hash: str, stack_trace=None ): return SimpleNamespace(present=True, url=None) def mock_get_build_status( client, template_id: str, build_id: str, logs_offset: int ): step = failure_map[template_id] reason = SimpleNamespace( message="Mocked API build error", log_entries=[], step=str(step) if step is not None else None, ) return SimpleNamespace( status=TemplateBuildStatus.ERROR, log_entries=[], reason=reason, ) monkeypatch.setattr(template_sync_main, "request_build", mock_request_build) monkeypatch.setattr(template_sync_main, "trigger_build", mock_trigger_build) monkeypatch.setattr( template_sync_main, "get_file_upload_link", mock_get_file_upload_link ) monkeypatch.setattr(build_api_mod, "get_build_status", mock_get_build_status) def _expect_to_throw_and_check_trace(func, expected_method: str): try: func() assert False, "Expected Template.build to raise an exception" except Exception as e: # noqa: BLE001 - we want to assert on the traceback regardless of type tb = e.__traceback__ saw_this_file = False saw_expected_method = False while tb is not None: traceback_file = tb.tb_frame.f_code.co_filename if traceback_file == __file__: saw_this_file = True caller_line = linecache.getline(traceback_file, tb.tb_lineno) if caller_line and f".{expected_method}(" in caller_line: saw_expected_method = True break tb = tb.tb_next assert saw_this_file, traceback.format_exc() assert saw_expected_method, traceback.format_exc() @pytest.mark.skip_debug() def test_traces_on_from_image(build): template = Template() template = template.from_image("e2b.dev/this-image-does-not-exist") _expect_to_throw_and_check_trace( lambda: build(template, name="from_image", skip_cache=True), "from_image" ) @pytest.mark.skip_debug() def test_traces_on_from_template(build): template = Template().from_template("this-template-does-not-exist") _expect_to_throw_and_check_trace( lambda: build(template, name="from_template", skip_cache=True), "from_template" ) @pytest.mark.skip_debug() def test_traces_on_from_dockerfile(build): template = Template() template = template.from_dockerfile("FROM ubuntu:22.04\nRUN nonexistent") _expect_to_throw_and_check_trace( lambda: build(template, name="from_dockerfile", skip_cache=True), "from_dockerfile", ) @pytest.mark.skip_debug() def test_traces_on_from_image_registry(build): template = Template() template = template.from_image( "registry.example.com/nonexistent:latest", username="test", password="test", ) _expect_to_throw_and_check_trace( lambda: build(template, name="from_image_registry", skip_cache=True), "from_image", ) @pytest.mark.skip_debug() def test_traces_on_from_image_credentials(): _expect_to_throw_and_check_trace( lambda: Template().from_image("ubuntu:22.04", username="user"), "from_image", ) @pytest.mark.skip_debug() def test_traces_on_from_aws_registry(build): template = Template() template = template.from_aws_registry( "123456789.dkr.ecr.us-east-1.amazonaws.com/nonexistent:latest", access_key_id="test", secret_access_key="test", region="us-east-1", ) _expect_to_throw_and_check_trace( lambda: build(template, name="from_aws_registry"), "from_aws_registry" ) @pytest.mark.skip_debug() def test_traces_on_from_gcp_registry(build): template = Template() template = template.from_gcp_registry( "gcr.io/nonexistent-project/nonexistent:latest", service_account_json={ "type": "service_account", }, ) _expect_to_throw_and_check_trace( lambda: build(template, name="from_gcp_registry"), "from_gcp_registry" ) @pytest.mark.skip_debug() def test_traces_on_copy(build): template = Template() template = template.from_base_image() template = template.skip_cache().copy(non_existent_path, non_existent_path) _expect_to_throw_and_check_trace(lambda: build(template, name="copy"), "copy") @pytest.mark.skip_debug() def test_traces_on_copyItems(build): template = Template() template = template.from_base_image() template = template.skip_cache().copy_items( [CopyItem(src=non_existent_path, dest=non_existent_path)] ) _expect_to_throw_and_check_trace( lambda: build(template, name="copy_items"), "copy_items" ) @pytest.mark.skip_debug() def test_traces_on_second_source_of_multi_source_copy(build): template = Template() template = template.from_base_image() template = template.copy(["test_stacktrace.py", "test_tags.py"], ".") _expect_to_throw_and_check_trace( lambda: build(template, name="multi_source_copy_second_source"), "copy" ) @pytest.mark.skip_debug() def test_traces_on_step_after_multi_source_copy(build): template = Template() template = template.from_base_image() template = template.copy(["test_stacktrace.py", "test_tags.py"], ".") template = template.run_cmd(f"cat {non_existent_path}") _expect_to_throw_and_check_trace( lambda: build(template, name="multi_source_copy_next_step"), "run_cmd" ) @pytest.mark.skip_debug() def test_traces_on_second_item_of_copy_items(build): template = Template() template = template.from_base_image() template = template.copy_items( [ CopyItem(src="test_stacktrace.py", dest="."), CopyItem(src="test_tags.py", dest="."), ] ) _expect_to_throw_and_check_trace( lambda: build(template, name="copy_items_second_item"), "copy_items" ) @pytest.mark.skip_debug() def test_traces_on_step_after_copy_items(build): template = Template() template = template.from_base_image() template = template.copy_items( [ CopyItem(src="test_stacktrace.py", dest="."), CopyItem(src="test_tags.py", dest="."), ] ) template = template.run_cmd(f"cat {non_existent_path}") _expect_to_throw_and_check_trace( lambda: build(template, name="copy_items_next_step"), "run_cmd" ) @pytest.mark.skip_debug() def test_traces_on_copy_absolute_path(): _expect_to_throw_and_check_trace( lambda: Template().from_base_image().copy("/absolute/path", "/absolute/path"), "copy", ) @pytest.mark.skip_debug() def test_traces_on_copyItems_absolute_path(): _expect_to_throw_and_check_trace( lambda: ( Template() .from_base_image() .copy_items([CopyItem(src="/absolute/path", dest="/absolute/path")]) ), "copy_items", ) @pytest.mark.skip_debug() def test_traces_on_remove(build): template = Template() template = template.from_base_image() template = template.skip_cache().remove(non_existent_path) _expect_to_throw_and_check_trace(lambda: build(template, name="remove"), "remove") @pytest.mark.skip_debug() def test_traces_on_rename(build): template = Template() template = template.from_base_image() template = template.skip_cache().rename(non_existent_path, "/tmp/dest.txt") _expect_to_throw_and_check_trace(lambda: build(template, name="rename"), "rename") @pytest.mark.skip_debug() def test_traces_on_make_dir(build): template = Template() template = template.from_base_image() template = template.set_user("root").skip_cache().make_dir("/root/.bashrc") _expect_to_throw_and_check_trace( lambda: build(template, name="make_dir"), "make_dir" ) @pytest.mark.skip_debug() def test_traces_on_make_symlink(build): template = Template() template = template.from_base_image() template = template.skip_cache().make_symlink(".bashrc", ".bashrc") _expect_to_throw_and_check_trace( lambda: build(template, name="make_symlink"), "make_symlink" ) @pytest.mark.skip_debug() def test_traces_on_run_cmd(build): template = Template() template = template.from_base_image() template = template.skip_cache().run_cmd(f"cat {non_existent_path}") _expect_to_throw_and_check_trace(lambda: build(template, name="run_cmd"), "run_cmd") @pytest.mark.skip_debug() def test_traces_on_set_workdir(build): template = Template() template = template.from_base_image() template = template.set_user("root").skip_cache().set_workdir("/root/.bashrc") _expect_to_throw_and_check_trace( lambda: build(template, name="set_workdir"), "set_workdir" ) @pytest.mark.skip_debug() def test_traces_on_set_user(build): template = Template() template = template.from_base_image() template = template.skip_cache().set_user("; exit 1") _expect_to_throw_and_check_trace( lambda: build(template, name="set_user"), "set_user" ) @pytest.mark.skip_debug() def test_traces_on_pip_install(build): template = Template() template = template.from_base_image() template = template.skip_cache().pip_install("nonexistent-package") _expect_to_throw_and_check_trace( lambda: build(template, name="pip_install"), "pip_install" ) @pytest.mark.skip_debug() def test_traces_on_npm_install(build): template = Template() template = template.from_base_image() template = template.skip_cache().npm_install("nonexistent-package") _expect_to_throw_and_check_trace( lambda: build(template, name="npm_install"), "npm_install" ) @pytest.mark.skip_debug() def test_traces_on_apt_install(build): template = Template() template = template.from_base_image() template = template.skip_cache().apt_install("nonexistent-package") _expect_to_throw_and_check_trace( lambda: build(template, name="apt_install"), "apt_install" ) @pytest.mark.skip_debug() def test_traces_on_git_clone(build): template = Template() template = template.from_base_image() template = template.skip_cache().git_clone("https://github.com/repo.git") _expect_to_throw_and_check_trace( lambda: build(template, name="git_clone"), "git_clone" ) @pytest.mark.skip_debug() def test_traces_on_set_start_cmd(build): template = Template() template = template.from_base_image() template = template.set_start_cmd( f"./{non_existent_path}", wait_for_timeout(10_000) ) _expect_to_throw_and_check_trace( lambda: build(template, name="set_start_cmd"), "set_start_cmd" ) @pytest.mark.skip_debug() def test_traces_on_add_mcp_server(): # needs mcp-gateway as base template, without it no mcp servers can be added _expect_to_throw_and_check_trace( lambda: Template().from_base_image().skip_cache().add_mcp_server("exa"), "add_mcp_server", ) @pytest.mark.skip_debug() def test_traces_on_dev_container_prebuild(build): template = Template() template = template.from_template("devcontainer") template = template.skip_cache().beta_dev_container_prebuild(non_existent_path) _expect_to_throw_and_check_trace( lambda: build(template, name="beta_dev_container_prebuild"), "beta_dev_container_prebuild", ) @pytest.mark.skip_debug() def test_traces_on_set_dev_container_start(build): template = Template() template = template.from_template("devcontainer") template = template.beta_set_dev_container_start(non_existent_path) _expect_to_throw_and_check_trace( lambda: build(template, name="beta_set_dev_container_start"), "beta_set_dev_container_start", )