|
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | 4 | import os |
| 5 | +from types import SimpleNamespace |
5 | 6 |
|
6 | 7 | import numpy as np |
7 | 8 | import pytest |
|
22 | 23 | from tidy3d.exceptions import SetupError |
23 | 24 | from tidy3d.web import common |
24 | 25 | from tidy3d.web.api.asynchronous import run_async |
25 | | -from tidy3d.web.api.container import Batch, Job |
| 26 | +from tidy3d.web.api.container import Batch, Job, WebContainer |
| 27 | +from tidy3d.web.api.run import _collect_by_hash, run |
| 28 | +from tidy3d.web.api.tidy3d_stub import Tidy3dStubData |
26 | 29 | from tidy3d.web.api.webapi import ( |
27 | 30 | abort, |
28 | 31 | delete, |
|
38 | 41 | load_simulation, |
39 | 42 | monitor, |
40 | 43 | real_cost, |
41 | | - run, |
42 | 44 | start, |
43 | 45 | upload, |
44 | 46 | ) |
|
55 | 57 | EST_FLEX_UNIT = 11.11 |
56 | 58 | FILE_SIZE_GB = 4.0 |
57 | 59 | common.CONNECTION_RETRY_TIME = 0.1 |
| 60 | +INVALID_TASK_ID = "INVALID_TASK_ID" |
58 | 61 |
|
59 | 62 | task_core_path = "tidy3d.web.core.task_core" |
60 | 63 | api_path = "tidy3d.web.api.webapi" |
@@ -768,13 +771,95 @@ def save_sim_to_path(path: str) -> None: |
768 | 771 | @responses.activate |
769 | 772 | def test_load_invalid_task_raises(mock_webapi): |
770 | 773 | """Ensure that load() raises TaskNotFoundError for a non-existent task ID.""" |
771 | | - fake_id = "INVALID_TASK_ID" |
772 | 774 |
|
773 | 775 | responses.add( |
774 | 776 | responses.GET, |
775 | | - f"{Env.current.web_api_endpoint}/tidy3d/tasks/{fake_id}/detail", |
| 777 | + f"{Env.current.web_api_endpoint}/tidy3d/tasks/{INVALID_TASK_ID}/detail", |
776 | 778 | json={"error": "Task not found"}, |
777 | 779 | status=404, |
778 | 780 | ) |
779 | 781 | with pytest.raises(WebNotFoundError, match="Resource not found"): |
780 | | - load(fake_id) |
| 782 | + load(INVALID_TASK_ID, replace_existing=True) |
| 783 | + |
| 784 | + |
| 785 | +def _fake_load_factory(tmp_root, taskid_to_sim: dict): |
| 786 | + def _fake_load(task_id, path="simulation_data.hdf5", lazy=False, **kwargs): |
| 787 | + abs_path = path if os.path.isabs(path) else os.path.join(tmp_root, path) |
| 788 | + abs_path = os.path.normpath(abs_path) |
| 789 | + os.makedirs(os.path.dirname(abs_path), exist_ok=True) |
| 790 | + |
| 791 | + sim_for_this = taskid_to_sim.get(task_id) |
| 792 | + |
| 793 | + log = "- Time step 827 / time 4.13e-14s ( 4 % done), field decay: 0.110e+00" |
| 794 | + sim_data = SimulationData(simulation=sim_for_this, data=[], diverged=False, log=log) |
| 795 | + |
| 796 | + sim_data.to_file(abs_path) |
| 797 | + return Tidy3dStubData.postprocess(abs_path, lazy=lazy) |
| 798 | + |
| 799 | + return _fake_load |
| 800 | + |
| 801 | + |
| 802 | +def apply_common_patches( |
| 803 | + monkeypatch, tmp_root, *, api_path="tidy3d.web.api.webapi", path_to_sim=None, taskid_to_sim=None |
| 804 | +): |
| 805 | + """Patch start/monitor/get_info/estimate_cost/upload/_check_folder/_modesolver_patch/load.""" |
| 806 | + monkeypatch.setattr(f"{api_path}.start", lambda *a, **k: True) |
| 807 | + monkeypatch.setattr(f"{api_path}.monitor", lambda *a, **k: True) |
| 808 | + monkeypatch.setattr(f"{api_path}.get_info", lambda *a, **k: SimpleNamespace(status="success")) |
| 809 | + monkeypatch.setattr(f"{api_path}.estimate_cost", lambda *a, **k: 0.0) |
| 810 | + monkeypatch.setattr(f"{api_path}.upload", lambda *a, **k: k["task_name"]) |
| 811 | + monkeypatch.setattr(WebContainer, "_check_folder", lambda *a, **k: True) |
| 812 | + monkeypatch.setattr(f"{api_path}._modesolver_patch", lambda *_, **__: None, raising=False) |
| 813 | + monkeypatch.setattr( |
| 814 | + f"{api_path}.load", |
| 815 | + _fake_load_factory(tmp_root=str(tmp_root), taskid_to_sim=taskid_to_sim), |
| 816 | + raising=False, |
| 817 | + ) |
| 818 | + |
| 819 | + |
| 820 | +@responses.activate |
| 821 | +def test_run_with_flexible_containers_offline_lazy(monkeypatch, tmp_path): |
| 822 | + sim1 = make_sim() |
| 823 | + sim2 = sim1.updated_copy(run_time=sim1.run_time / 2) |
| 824 | + sim_container = [sim1, {"sim": sim1, "sim2": sim2}, (sim1, [sim2])] |
| 825 | + |
| 826 | + h2sim = _collect_by_hash(sim_container) |
| 827 | + task_name = "T" |
| 828 | + out_dir = tmp_path / "out" |
| 829 | + |
| 830 | + taskid_to_sim = {f"{task_name}_{h}": s for h, s in h2sim.items()} |
| 831 | + |
| 832 | + apply_common_patches(monkeypatch, tmp_path, taskid_to_sim=taskid_to_sim) |
| 833 | + |
| 834 | + data = run(sim_container, task_name=task_name, folder_name="PROJECT", path=str(out_dir)) |
| 835 | + |
| 836 | + assert isinstance(data, list) and len(data) == 3 |
| 837 | + |
| 838 | + assert isinstance(data[0], SimulationData) |
| 839 | + assert data[0].__class__.__name__ == "SimulationDataProxy" |
| 840 | + |
| 841 | + assert isinstance(data[1], dict) |
| 842 | + assert "sim2" in data[1] |
| 843 | + assert isinstance(data[1]["sim2"], SimulationData) |
| 844 | + assert data[1]["sim2"].__class__.__name__ == "SimulationDataProxy" |
| 845 | + |
| 846 | + assert isinstance(data[2], tuple) |
| 847 | + assert data[2][0].__class__.__name__ == "SimulationDataProxy" |
| 848 | + assert isinstance(data[2][1], list) |
| 849 | + assert data[2][1][0].__class__.__name__ == "SimulationDataProxy" |
| 850 | + |
| 851 | + assert data[0].simulation == sim1 |
| 852 | + assert data[1]["sim2"].simulation == sim2 |
| 853 | + |
| 854 | + |
| 855 | +@responses.activate |
| 856 | +def test_run_single_offline_eager(monkeypatch, tmp_path): |
| 857 | + sim = make_sim() |
| 858 | + single_file = str(tmp_path / "sim.hdf5") |
| 859 | + task_name = "single" |
| 860 | + apply_common_patches(monkeypatch, tmp_path, taskid_to_sim={task_name: sim}) |
| 861 | + |
| 862 | + sim_data = run(sim, task_name=task_name, path=single_file) |
| 863 | + |
| 864 | + assert isinstance(sim_data, SimulationData) |
| 865 | + assert sim_data.__class__.__name__ == "SimulationData" # no proxy |
0 commit comments