Skip to content

Replace-experiment-client-with-ert-storage-api - #14272

Open
eqbech wants to merge 1 commit into
replace-remaining-usages-of-old-clientfrom
replace-experiment-client-with-ert-storage-api
Open

Replace-experiment-client-with-ert-storage-api#14272
eqbech wants to merge 1 commit into
replace-remaining-usages-of-old-clientfrom
replace-experiment-client-with-ert-storage-api

Conversation

@eqbech

@eqbech eqbech commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Issue
Resolves #14244

Approach
Short description of the approach

(Screenshot of new behavior in GUI if applicable)

  • PR title captures the intent of the changes, and is fitting for release notes.
  • Added appropriate release note label
  • Commit history is consistent and clean, in line with the contribution guidelines.
  • Make sure unit tests pass locally after every commit (git rebase -i main --exec 'just rapid-tests')

When applicable

  • When screenshots are changed: Review screenshot-PR in ert-testdata,
    merge screenshot-PR in ert-testdata before merging this PR.
  • When there are user facing changes: Updated documentation
  • New behavior or changes to existing untested code: Ensured that unit tests are added (See Ground Rules).
  • Large PR: Prepare changes in small commits for more convenient review
  • Bug fix: Add regression test for the bug
  • Bug fix: Add backport label to latest release (format: 'backport release-branch-name')

Stack created with GitHub Stacks CLIGive Feedback 💬

@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 99bbd73 to 8989adc Compare August 25, 2026 07:48
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch 3 times, most recently from bb31223 to b232445 Compare August 25, 2026 08:39
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

❌ 7 Tests Failed:

Tests completed Failed Passed Skipped
3597 7 3590 63
View the top 3 failed test(s) by shortest run time
tests/ert/unit_tests/services/test_storage_service.py::test_storage_logging
Stack Traces | 3.43s run time
change_to_tmpdir = None

    @pytest.mark.skip_mac_ci  # Slow/failing - fqdn issue?
    @pytest.mark.slow
    def test_storage_logging(change_to_tmpdir):
        """
        This is a regression test for a bug where the storage service
        would log everything twice
        """
    
        with ErtServerController.start_server(
            verbose=True,
            project=Path("."),
            parent_pid=os.getpid(),
        ) as server:
>           assert server.wait_until_ready(), "StorageService failed to start"
E           AssertionError: StorageService failed to start
E           assert False
E            +  where False = wait_until_ready()
E            +    where wait_until_ready = <ert.services.ert_server.ErtServerController object at 0x7f891e9d56a0>.wait_until_ready

.../unit_tests/services/test_storage_service.py:171: AssertionError
tests/ert/ui_tests/cli/test_cli.py::test_that_killed_ert_does_not_leave_storage_server_process
Stack Traces | 11.5s run time
@pytest.mark.usefixtures("copy_poly_case")
    @pytest.mark.skipif(
        sys.platform.startswith("darwin"), reason="Currently failing on mac"
    )
    async def test_that_killed_ert_does_not_leave_storage_server_process():
        ert_subprocess = Popen(["ert", "gui", "poly.ert"])
        assert ert_subprocess.is_running()
    
        async def _find_storage_process_pid() -> int:
            while True:
                for ert_child_process in ert_subprocess.children():
                    try:
                        if "storage" in "".join(ert_child_process.cmdline()):
                            return ert_child_process.pid
                    except (ZombieProcess, NoSuchProcess):
                        pass
                await asyncio.sleep(0.05)
    
        storage_process_pid = await asyncio.wait_for(
            _find_storage_process_pid(), timeout=120
        )
        # wait for storage server to have connected to ert
        await asyncio.sleep(5)
>       storage_process = Process(storage_process_pid)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.../ui_tests/cli/test_cli.py:924: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../ert/ert/.venv/lib/python3.12....../site-packages/psutil/__init__.py:314: in __init__
    self._init(pid)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = psutil.Process(pid=11000, status='terminated'), pid = 11000
_ignore_nsp = False

    def _init(self, pid, _ignore_nsp=False):
        if pid is None:
            pid = os.getpid()
        else:
            if pid < 0:
                msg = f"pid must be a positive integer (got {pid})"
                raise ValueError(msg)
            try:
                _psplatform.cext.check_pid_range(pid)
            except OverflowError as err:
                msg = "process PID out of range"
                raise NoSuchProcess(pid, msg=msg) from err
    
        self._pid = pid
        self._name = None
        self._exe = None
        self._create_time = None
        self._gone = False
        self._pid_reused = False
        self._hash = None
        self._lock = threading.RLock()
        # used for caching on Windows only (on POSIX ppid may change)
        self._ppid = None
        # platform-specific modules define an _psplatform.Process
        # implementation class
        self._proc = _psplatform.Process(pid)
        self._last_sys_cpu_times = None
        self._last_proc_cpu_times = None
        self._exitcode = _SENTINEL
        self._ident = (self.pid, None)
        try:
            self._ident = self._get_ident()
        except AccessDenied:
            # This should happen on Windows only, since we use the fast
            # create time method. AFAIK, on all other platforms we are
            # able to get create time for all PIDs.
            pass
        except ZombieProcess:
            # Zombies can still be queried by this class (although
            # not always) and pids() return them so just go on.
            pass
        except NoSuchProcess:
            if not _ignore_nsp:
                msg = "process PID not found"
>               raise NoSuchProcess(pid, msg=msg) from None
E               psutil.NoSuchProcess: process PID not found (pid=11000)

.../ert/ert/.venv/lib/python3.12....../site-packages/psutil/__init__.py:360: NoSuchProcess
tests/ert/ui_tests/gui/test_main_window.py::test_that_gui_plotter_works_when_no_data
Stack Traces | 241s run time
qtbot = <pytestqt.qtbot.QtBot object at 0x7f9b32e97ce0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f9b3306b710>
use_tmpdir = None

    @pytest.mark.skip_mac_ci
    @pytest.mark.usefixtures("use_site_configurations_with_no_queue_options")
    def test_that_gui_plotter_works_when_no_data(qtbot, monkeypatch, use_tmpdir):
        monkeypatch.setattr(PlotApi, "get_all_ensembles", lambda _: [])
        config_file = "minimal_config.ert"
        Path(config_file).write_text(
            "NUM_REALIZATIONS 1\nENSPATH storage\nQUEUE_SYSTEM LOCAL", encoding="utf-8"
        )
    
        args_mock = Mock()
        args_mock.config = config_file
        ert_config = ErtConfig.from_file(config_file)
        # Open up storage to create it, so that dark storage can be mounted onto it
        # Not creating will result in dark storage hanging/lagging
        open_storage(ert_config.ens_path, mode="r")
    
        with ErtServerController.init_service(
            project=Path(ert_config.ens_path).absolute(),
        ):
            gui = _setup_main_window(
                ert_config, args_mock, GUILogHandler(), ert_config.ens_path
            )
            qtbot.addWidget(gui)
    
            button_plot_tool = gui.findChild(QToolButton, "button_Create_plot")
            assert button_plot_tool
            qtbot.mouseClick(button_plot_tool, Qt.MouseButton.LeftButton)
            plot_window = wait_for_child(gui, qtbot, PlotWindow)
    
>           ensemble_plot_names = get_child(
                plot_window, EnsembleSelectListWidget, "ensemble_selector"
            ).get_checked_ensembles()

.../ui_tests/gui/test_main_window.py:667: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

gui = <ert.gui.plotting.plot_window.PlotWindow object at 0x7f9b281dd770>
typ = <class 'ert.gui.plotting.widgets.plot_ensemble_selection_widget.EnsembleSelectListWidget'>
args = ('ensemble_selector',), kwargs = {}

    def get_child[V](gui: QWidget, typ: type[V], *args, **kwargs) -> V:
        child = gui.findChild(typ, *args, **kwargs)
>       assert isinstance(child, typ)
E       AssertionError: assert False
E        +  where False = isinstance(None, <class 'ert.gui.plotting.widgets.plot_ensemble_selection_widget.EnsembleSelectListWidget'>)

.../ui_tests/gui/conftest.py:435: AssertionError
tests/ert/ui_tests/gui/test_plotting_of_snake_oil.py::test_that_all_plotter_filter_boxes_yield_expected_filter_results[0]
Stack Traces | 241s run time
qtbot = <pytestqt.qtbot.QtBot object at 0x7f9b33032960>
snake_oil_case_storage = ErtConfig(substitutions={'<CONFIG_PATH>': '.../pytest-0/test_that_all_plotter_filter_b0/test_data', ...ndomSeedGenerator(user_defined_seed=3593114179000630026631423308983283277868), shape_registry=ShapeRegistry(shapes={}))

    @pytest.mark.skip_mac_ci
    def test_that_all_plotter_filter_boxes_yield_expected_filter_results(
        qtbot, snake_oil_case_storage
    ):
        args_mock = Mock()
        args_mock.config = "snake_oil.ert"
    
        log_handler = GUILogHandler()
        with (
            ErtServerController.init_service(
                project=Path(snake_oil_case_storage.ens_path).absolute(),
            ),
        ):
            gui = _setup_main_window(
                snake_oil_case_storage,
                args_mock,
                log_handler,
                snake_oil_case_storage.ens_path,
            )
            qtbot.addWidget(gui)
    
            button_plot_tool = gui.findChild(QToolButton, "button_Create_plot")
            assert button_plot_tool
            qtbot.mouseClick(button_plot_tool, Qt.MouseButton.LeftButton)
            plot_window = wait_for_child(gui, qtbot, PlotWindow)
    
>           key_list = plot_window.findChild(DataTypeKeysWidget).data_type_keys_widget
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           AttributeError: 'NoneType' object has no attribute 'data_type_keys_widget'

.../ui_tests/gui/test_plotting_of_snake_oil.py:166: AttributeError
tests/ert/unit_tests/gui/tools/plot/test_plot_window.py::test_warning_is_visible_on_incompatible_plot_api_version
Stack Traces | 242s run time
qtbot = <pytestqt.qtbot.QtBot object at 0x7f2aaabc46b0>
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_warning_is_visible_on_inc0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f2abce41b80>
use_tmpdir = None

    @pytest.mark.slow
    def test_warning_is_visible_on_incompatible_plot_api_version(
        qtbot: QtBot, tmp_path, monkeypatch, use_tmpdir
    ):
        mock_get_data = MagicMock()
        mock_get_data.return_value = "0.2"
        monkeypatch.setattr("ert.gui.plotting.plot_api.PlotApi.api_version", mock_get_data)
    
        with ErtServerController.init_service(project=tmp_path):
>           pw = PlotWindow("", tmp_path, None)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.../tools/plot/test_plot_window.py:202: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../src/ert/utils.py:29: in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
.../gui/plotting/plot_window.py:169: in __init__
    self._api = PlotApi(ens_path)
                ^^^^^^^^^^^^^^^^^
.../gui/plotting/plot_api.py:59: in __init__
    self._client = ert_client or ErtClient.for_project(ens_path)
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../ert/services/ert_client.py:120: in for_project
    return cls(SharedClient.get_client(project, connect_timeout), timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../ert/services/shared_client.py:58: in get_client
    client = create_ertserver_client(key, timeout=timeout)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../ert/services/shared_client.py:140: in create_ertserver_client
    controller = create_ert_server_controller(timeout=timeout, project=project)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

project = PosixPath('.../pytest-0/popen-gw1/test_warning_is_visible_on_inc0')
timeout = 240, logging_config = None

    def create_ert_server_controller(
        *,
        project: os.PathLike[str],
        timeout: int | None = None,
        logging_config: str | None = None,
    ) -> ErtServerController:
        path = Path(project)
        # Wait for storage_server.json file to appear
        try:  # ruff: ignore[too-many-statements-in-try-clause]
            if timeout is None:
                timeout = 240
            t = -1
            while t < timeout:
                storage_server_path = path / _ERT_SERVER_CONNECTION_INFO_FILE
                try:
                    if storage_server_path.stat().st_size > 0:
                        storage_server_content = json.loads(
                            storage_server_path.read_text(encoding="utf-8")
                        )
    
                        return ErtServerController(
                            storage_path=str(path),
                            connection_info=storage_server_content,
                            logging_config=logging_config,
                        )
                except FileNotFoundError:
                    pass
    
                sleep(1)
                t += 1
    
>           raise TimeoutError("Server not started")
E           TimeoutError: Server not started

.../ert/services/ert_server.py:452: TimeoutError
tests/ert/ui_tests/gui/test_main_window.py::test_right_click_plot_button_opens_external_plotter
Stack Traces | 246s run time
qtbot = <pytestqt.qtbot.QtBot object at 0x7f9b32e8bec0>, use_tmpdir = None
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f9b32e8bdd0>

    @pytest.mark.skip_mac_ci
    @pytest.mark.usefixtures("use_tmpdir", "use_site_configurations_with_no_queue_options")
    def test_right_click_plot_button_opens_external_plotter(qtbot, use_tmpdir, monkeypatch):
        monkeypatch.setattr(PlotApi, "get_all_ensembles", lambda _: [])
        config_file = "minimal_config.ert"
        Path(config_file).write_text(
            "NUM_REALIZATIONS 1\nENSPATH storage\nQUEUE_SYSTEM LOCAL", encoding="utf-8"
        )
    
        # Open up storage to create it, so that dark storage can be mounted onto it
        # Not creating will result in dark storage hanging/lagging
        open_storage("storage", mode="r")
    
        args_mock = Mock()
        args_mock.config = config_file
        ert_config = ErtConfig.from_file(config_file)
        with ErtServerController.init_service(
            project=Path(ert_config.ens_path).absolute(),
        ):
            gui = _setup_main_window(
                ert_config, args_mock, GUILogHandler(), ert_config.ens_path
            )
            qtbot.addWidget(gui)
    
            button_plot_tool = gui.findChild(QToolButton, "button_Create_plot")
            assert button_plot_tool
    
            def top_level_plotter_windows() -> list[PlotWindow]:
                plot_windows = gui.get_external_plot_windows()
    
                return [
                    win
                    for win in plot_windows
                    if "Plotting" in win.windowTitle() and win.isVisible()
                ]
    
            def right_click_plotter_button() -> None:
                top_level_windows = len(top_level_plotter_windows())
                qtbot.mouseClick(button_plot_tool, Qt.MouseButton.RightButton)
                qtbot.wait_until(
                    lambda: len(top_level_plotter_windows()) > top_level_windows,
                    timeout=5000,
                )
    
>           right_click_plotter_button()

.../ui_tests/gui/test_main_window.py:717: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def right_click_plotter_button() -> None:
        top_level_windows = len(top_level_plotter_windows())
        qtbot.mouseClick(button_plot_tool, Qt.MouseButton.RightButton)
>       qtbot.wait_until(
            lambda: len(top_level_plotter_windows()) > top_level_windows,
            timeout=5000,
        )
E       pytestqt.exceptions.TimeoutError: waitUntil timed out in 5000 milliseconds

.../ui_tests/gui/test_main_window.py:712: TimeoutError
tests/ert/ui_tests/gui/test_main_window.py::test_that_the_plot_window_contains_the_expected_elements
Stack Traces | 252s run time
esmda_has_run = <ert.gui.main_window.ErtMainWindow object at 0x7f9b29da5a90>
qtbot = <pytestqt.qtbot.QtBot object at 0x7f9b33078110>

    @pytest.mark.skip_mac_ci
    def test_that_the_plot_window_contains_the_expected_elements(
        esmda_has_run: ErtMainWindow, qtbot
    ):
        gui = esmda_has_run
        open_storage(gui.ert_config.ens_path, mode="r")
        with ErtServerController.init_service(
            project=Path(gui.ert_config.ens_path).absolute(),
        ):
            expected_ensembles = [
                "es_mda : iter-0",
                "es_mda : iter-1",
                "es_mda : iter-2",
                "es_mda : iter-3",
            ]
    
            # Click on Create plot after esmda has run
            button_plot_tool = gui.findChild(QToolButton, "button_Create_plot")
            assert button_plot_tool
            qtbot.mouseClick(button_plot_tool, Qt.MouseButton.LeftButton)
            plot_window = wait_for_child(gui, qtbot, PlotWindow)
    
>           data_types = get_child(plot_window, DataTypeKeysWidget)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.../ui_tests/gui/test_main_window.py:310: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

gui = <ert.gui.plotting.plot_window.PlotWindow object at 0x7f9b29df1f90>
typ = <class 'ert.gui.plotting.widgets.data_type_keys_widget.DataTypeKeysWidget'>
args = (), kwargs = {}

    def get_child[V](gui: QWidget, typ: type[V], *args, **kwargs) -> V:
        child = gui.findChild(typ, *args, **kwargs)
>       assert isinstance(child, typ)
E       AssertionError: assert False
E        +  where False = isinstance(None, <class 'ert.gui.plotting.widgets.data_type_keys_widget.DataTypeKeysWidget'>)

.../ui_tests/gui/conftest.py:435: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch 2 times, most recently from 5d3e64b to f4dc6c1 Compare August 26, 2026 07:07
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from f4dc6c1 to 88c64ef Compare August 26, 2026 10:00
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch 2 times, most recently from 4be5f87 to c8f1bd0 Compare August 27, 2026 06:03
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from c8f1bd0 to ba1be06 Compare August 27, 2026 06:16
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch 3 times, most recently from 9ca8eec to 79f0cb6 Compare August 27, 2026 07:30
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 79f0cb6 to cb1cf40 Compare August 27, 2026 08:45
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from cb1cf40 to 27bbdf6 Compare August 27, 2026 10:06
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 27bbdf6 to 39818c0 Compare August 27, 2026 10:12
@ertomatic

Copy link
Copy Markdown
Collaborator

Screenshots differ from baselines. A baseline update PR has been prepared: equinor/ert-testdata#105

ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 28, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 6ea7611 to 893422d Compare August 31, 2026 07:48
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 893422d to a705785 Compare August 31, 2026 11:04
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from a705785 to 2727212 Compare August 31, 2026 13:04
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 2727212 to bc93bef Compare August 31, 2026 13:33
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from bc93bef to 16ddc53 Compare August 31, 2026 14:00
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from 16ddc53 to 98e5cce Compare August 31, 2026 14:03
ertomatic pushed a commit to equinor/ert-testdata that referenced this pull request Aug 31, 2026
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch 2 times, most recently from 07efd06 to bd81e6b Compare September 1, 2026 10:46
@eqbech
eqbech force-pushed the replace-experiment-client-with-ert-storage-api branch from bd81e6b to e3e6157 Compare September 1, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a common ert/everest client

3 participants