From a8bf9f722c6d5b1575e3a4e872814e6328e52236 Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 17 Oct 2024 16:41:57 +0200 Subject: [PATCH 1/9] Add e2e tests for `ReadOnlyRun` --- tests/README.md | 27 +++- tests/e2e/conftest.py | 79 +++++++++++- tests/e2e/test_read_only_run.py | 127 +++++++++++++++++++ tests/e2e/{test_misc.py => test_run_list.py} | 0 4 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/test_read_only_run.py rename tests/e2e/{test_misc.py => test_run_list.py} (100%) diff --git a/tests/README.md b/tests/README.md index df9efc5..3d4415f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,8 @@ -# Important note +# Important notes -All tests in the `e2e` directory are end-to-end tests that assume a +## Prepopulated test data + +A subset of the tests in the `e2e` directory are end-to-end tests that assume a specific data layout in a project. This data is populated using the `populate_projects.py` script, which @@ -8,3 +10,24 @@ has comments explaining the layout. The script *must* be run only **once** on a fresh project to set up the necessary data for the tests to run successfully. + +The tests that rely on this data are: + +* `test_dataframe_values.py` +* `test_run_filtering.py` +* `test_run_list.py` + +## Environment variables + +* `NEPTUNE_PROJECT` - project name to use. It is set by the test runner script in + GitHub setup. +* `NEPTUNE_API_TOKEN` - API token to use + +Test that populate data during execution could interfere with those that rely on a +specific data shape. To avoid this, set `NEPTUNE_E2E_PROJECT` to a different project. + +If the env variable `NEPTUNE_E2E_CUSTOM_RUN_ID` is set, it should be `sys/custom_run_id` +of an existing Run. + +This Run will be used for all tests in `test_read_only_run.py`. Otherwise, a new +Run will be created and used for the tests. diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 171a07f..7bcb7f0 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -1,6 +1,18 @@ +import os +import uuid +from datetime import ( + datetime, + timezone, +) +from typing import Callable + +from neptune_scale import Run from pytest import fixture -from neptune_fetcher import ReadOnlyProject +from neptune_fetcher import ( + ReadOnlyProject, + ReadOnlyRun, +) # NOTE # The fixtures below assume that we're testing on a "many metrics" project @@ -50,3 +62,68 @@ def id_to_name(): d |= {f"id-exp-{num}": f"exp{num}" for num in range(1, 7)} return d + + +class SyncRun: + """Wraps a neptune_scale.Run instance to make it wait for processing + after each logging method call. This is useful for e2e tests, where we + usually want to wait for the data to be available before fetching it.""" + + def __init__(self, run): + self.run = run + + def __getattr__(self, name): + attr = getattr(self.run, name) + + # Wrap only log*() methods, don't wrap already wrapped ones + if name.startswith("log") and callable(attr) and not hasattr(attr, "_is_wrapped"): + return self._wrap(attr) + + return attr + + def _wrap(self, fn: Callable): + def wrapped(*args, **kwargs): + result = fn(*args, **kwargs) + self.run.wait_for_processing() + return result + + wrapped._is_wrapped = True + setattr(self.run, fn.__name__, wrapped) + + return wrapped + + +@fixture +def sync_run(run): + """Blocking run for logging data""" + return SyncRun(run) + + +@fixture(scope="session") +def run(): + """Plain neptune_scale.Run instance. We're scoping it to "session", as it seems to be a + good compromise, mostly because of execution time.""" + + # TODO: if a test fails the run could be left in an indefinite state + # Maybe we should just have it scoped 'function' and require passing + # an existing run id + kwargs = {} + run_id = os.getenv("NEPTUNE_E2E_CUSTOM_RUN_ID") + if run_id is None: + run_id = str(uuid.uuid4()) + kwargs["experiment_name"] = "pye2e-fetcher" + else: + kwargs["resume"] = True + + kwargs["run_id"] = kwargs["family"] = run_id + + run = Run(**kwargs) + run.log_configs({"test_start_time": datetime.now(timezone.utc)}) + + return run + + +@fixture +def ro_run(run, project): + """ReadOnlyRun pointing to the same run as the neptune_scale.Run""" + return ReadOnlyRun(read_only_project=project, custom_id=run._run_id) diff --git a/tests/e2e/test_read_only_run.py b/tests/e2e/test_read_only_run.py new file mode 100644 index 0000000..f22c11b --- /dev/null +++ b/tests/e2e/test_read_only_run.py @@ -0,0 +1,127 @@ +import random +import time +import uuid +from datetime import ( + datetime, + timezone, +) + + +def unique_path(prefix): + return f"{prefix}__{datetime.now(timezone.utc).isoformat('-', 'seconds')}__{str(uuid.uuid4())[-4:]}" + + +def random_series(length=10, start_step=0): + """Return a 2-tuple of step and value lists, both of length `length`""" + assert length > 0 + assert start_step >= 0 + + j = random.random() + # Round to 0 to avoid floating point errors + steps = [round((j + x) ** 2.0, 0) for x in range(start_step, length)] + values = [round((j + x) ** 3.0, 0) for x in range(len(steps))] + + return steps, values + + +def test_atoms(sync_run, ro_run): + """Set atoms to a value, make sure it's equal when fetched""" + + now = time.time() + data = { + "int-value": int(now), + "float-value": now, + "str-value": f"hello-{now}", + "true-value": True, + "false-value": False, + # The backend rounds the milliseconds component, so we're fine with just 0 to be more predictable + "datetime-value": datetime.now(timezone.utc).replace(microsecond=0), + } + + sync_run.log_configs(data) + + for key, value in data.items(): + assert ro_run[key].fetch() == value, f"Value for {key} does not match" + + # Replace the data and make sure the update is reflected AFTER we purge the cache for those items + updated_data = { + "int-value": int(now + 1), + "float-value": now + 1, + "str-value": f"hello-{now + 1}", + "true-value": False, + "false-value": True, + "datetime-value": datetime.now(timezone.utc).replace(year=1999, microsecond=0), + } + + sync_run.log_configs(updated_data) + + # The data should stay the same, as we haven't purged the cache yet + for key, value in data.items(): + assert ro_run[key].fetch() == value, f"The cached value for {key} does not match" + + # Now purge the cache for the logged items, and fetch them again. They should now have the new values + for key in data.keys(): + del ro_run[key] + + for key, value in updated_data.items(): + assert ro_run[key].fetch() == value, f"The updated value for {key} does not match" + + +def test_series_no_prefetch(run, ro_run): + path = unique_path("test_series/series_no_prefetch") + + steps, values = random_series() + + for step, value in zip(steps, values): + run.log_metrics(data={path: value}, step=step) + + run.wait_for_processing() + + df = ro_run[path].fetch_values() + assert df["step"].tolist() == steps + assert df["value"].tolist() == values + + +def test_series_with_prefetch(run, ro_run): + path = unique_path("test_series/series_with_prefetch") + + steps, values = random_series() + + for step, value in zip(steps, values): + run.log_metrics(data={path: value}, step=step) + + run.wait_for_processing() + + ro_run.prefetch_series_values([path]) + df = ro_run[path].fetch_values() + + assert df["step"].tolist() == steps + assert df["value"].tolist() == values + + +def test_series_fetch_and_append(run, ro_run): + """Fetch a series, then append, then fetch again -- the new data points should be there""" + + path = unique_path("test_series/series_no_prefetch") + + steps, values = random_series() + + for step, value in zip(steps, values): + run.log_metrics(data={path: value}, step=step) + + run.wait_for_processing() + + df = ro_run[path].fetch_values() + assert df["step"].tolist() == steps + assert df["value"].tolist() == values + + steps2, values2 = random_series(length=5, start_step=len(steps)) + + for step, value in zip(steps2, values2): + run.log_metrics(data={path: value}, step=step) + + run.wait_for_processing() + + df = ro_run[path].fetch_values() + assert df["step"].tolist() == steps + steps2 + assert df["value"].tolist() == values + values2 diff --git a/tests/e2e/test_misc.py b/tests/e2e/test_run_list.py similarity index 100% rename from tests/e2e/test_misc.py rename to tests/e2e/test_run_list.py From ece979c9703e699b9e95e81e572665b55bb4eec4 Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 17 Oct 2024 17:40:37 +0200 Subject: [PATCH 2/9] Allow overrding NEPTUNE_PROJECT per test module --- .github/workflows/tests-e2e.yml | 2 ++ tests/e2e/conftest.py | 24 +++++++++++++++--------- tests/e2e/test_read_only_run.py | 3 +++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 880bf2f..5ee7f05 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -28,7 +28,9 @@ jobs: - name: Run tests env: NEPTUNE_WORKSPACE: ${{ secrets.E2E_WORKSPACE }} + NEPTUNE_E2E_PROJECT: ${{ secrets.E2E_PROJECT }} NEPTUNE_API_TOKEN: ${{ secrets.E2E_API_TOKEN }} + NEPTUNE_E2E_CUSTOM_RUN_ID: ${{ vars.E2E_CUSTOM_RUN_ID }} run: bash .github/scripts/run-e2e-tests.sh - name: Upload test reports diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 7bcb7f0..c7a13e2 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -22,12 +22,18 @@ # around names and values of the metadata. -@fixture(scope="session") -def project(): - # Assume project name and API token are set in the environment. - # As ReadOnlyProject is basically stateless, we can reuse the same - # instance across all tests. - return ReadOnlyProject() +@fixture(scope="module") +def project(request): + # Assume the project name and API token are set in the environment using the standard + # NEPTUNE_PROJECT and NEPTUNE_API_TOKEN variables. + # + # Since ReadOnlyProject is essentially stateless, we can reuse the same + # instance across all tests in a module. + # + # We also allow overriding the project name per module by setting the + # module-level `NEPTUNE_PROJECT` variable. + project_name = getattr(request.module, "NEPTUNE_PROJECT", None) + return ReadOnlyProject(project=project_name) @fixture @@ -99,15 +105,15 @@ def sync_run(run): return SyncRun(run) -@fixture(scope="session") -def run(): +@fixture(scope="module") +def run(project): """Plain neptune_scale.Run instance. We're scoping it to "session", as it seems to be a good compromise, mostly because of execution time.""" # TODO: if a test fails the run could be left in an indefinite state # Maybe we should just have it scoped 'function' and require passing # an existing run id - kwargs = {} + kwargs = {"project": project.project_identifier} run_id = os.getenv("NEPTUNE_E2E_CUSTOM_RUN_ID") if run_id is None: run_id = str(uuid.uuid4()) diff --git a/tests/e2e/test_read_only_run.py b/tests/e2e/test_read_only_run.py index f22c11b..be19316 100644 --- a/tests/e2e/test_read_only_run.py +++ b/tests/e2e/test_read_only_run.py @@ -1,3 +1,4 @@ +import os import random import time import uuid @@ -6,6 +7,8 @@ timezone, ) +NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_PROJECT") + def unique_path(prefix): return f"{prefix}__{datetime.now(timezone.utc).isoformat('-', 'seconds')}__{str(uuid.uuid4())[-4:]}" From 8abc0659fb65e63ecc87bb1211fee9085fc7b17a Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 17 Oct 2024 18:00:00 +0200 Subject: [PATCH 3/9] Update README --- tests/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/README.md b/tests/README.md index 3d4415f..6011883 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,10 +24,9 @@ The tests that rely on this data are: * `NEPTUNE_API_TOKEN` - API token to use Test that populate data during execution could interfere with those that rely on a -specific data shape. To avoid this, set `NEPTUNE_E2E_PROJECT` to a different project. +specific data shape. To avoid this, set `NEPTUNE_E2E_PROJECT` to a different project +that already exists. If the env variable `NEPTUNE_E2E_CUSTOM_RUN_ID` is set, it should be `sys/custom_run_id` -of an existing Run. - -This Run will be used for all tests in `test_read_only_run.py`. Otherwise, a new -Run will be created and used for the tests. +of an existing Run. This avoids creating a new Run for tests that log data, if this +is for some reason required. From 59196c6da936136d563aaaa26e1671c42d687d4b Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Fri, 18 Oct 2024 14:48:44 +0200 Subject: [PATCH 4/9] Fix a comment --- tests/e2e/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index c7a13e2..60bbd07 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -107,7 +107,7 @@ def sync_run(run): @fixture(scope="module") def run(project): - """Plain neptune_scale.Run instance. We're scoping it to "session", as it seems to be a + """Plain neptune_scale.Run instance. We're scoping it to "module", as it seems to be a good compromise, mostly because of execution time.""" # TODO: if a test fails the run could be left in an indefinite state From 8fd82758891cae66ec8a044e565ed60c5afecb66 Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Tue, 22 Oct 2024 18:13:04 +0200 Subject: [PATCH 5/9] Update README.md and simplify `SyncRun` --- tests/README.md | 20 +++++------ tests/e2e/conftest.py | 58 ++++++++++++------------------ tests/e2e/test_dataframe_values.py | 3 ++ tests/e2e/test_run_filtering.py | 4 +++ tests/e2e/test_run_list.py | 5 +++ 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/README.md b/tests/README.md index 6011883..06ca9d4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -19,14 +19,14 @@ The tests that rely on this data are: ## Environment variables -* `NEPTUNE_PROJECT` - project name to use. It is set by the test runner script in - GitHub setup. * `NEPTUNE_API_TOKEN` - API token to use - -Test that populate data during execution could interfere with those that rely on a -specific data shape. To avoid this, set `NEPTUNE_E2E_PROJECT` to a different project -that already exists. - -If the env variable `NEPTUNE_E2E_CUSTOM_RUN_ID` is set, it should be `sys/custom_run_id` -of an existing Run. This avoids creating a new Run for tests that log data, if this -is for some reason required. +* `NEPTUNE_E2E_FIXED_PROJECT` - project name to use for tests that require a project + with fixed data populated by `populate_projects.py` and **do not** populate the + project during execution. The test runner script creates a temporary project for + that purpose. If not set, `NEPTUNE_PROJECT` is used. +* `NEPTUNE_E2E_PROJECT` - project name to use for tests that create Runs during + execution. This can be an existing project in which it's fine to create multiple + Runs with different data. If not set, `NEPTUNE_PROJECT` is used. +* `NEPTUNE_E2E_CUSTOM_RUN_ID` (optional) - if set, it should be `sys/custom_run_id` + of an existing Run. This avoids creating a new Run for tests that log data, + if this is for some reason required. diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 60bbd07..4bedbfc 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -4,7 +4,6 @@ datetime, timezone, ) -from typing import Callable from neptune_scale import Run from pytest import fixture @@ -70,45 +69,20 @@ def id_to_name(): return d -class SyncRun: - """Wraps a neptune_scale.Run instance to make it wait for processing +class SyncRun(Run): + """A neptune_scale.Run instance that waits for processing to complete after each logging method call. This is useful for e2e tests, where we usually want to wait for the data to be available before fetching it.""" - def __init__(self, run): - self.run = run - - def __getattr__(self, name): - attr = getattr(self.run, name) - - # Wrap only log*() methods, don't wrap already wrapped ones - if name.startswith("log") and callable(attr) and not hasattr(attr, "_is_wrapped"): - return self._wrap(attr) - - return attr - - def _wrap(self, fn: Callable): - def wrapped(*args, **kwargs): - result = fn(*args, **kwargs) - self.run.wait_for_processing() - return result - - wrapped._is_wrapped = True - setattr(self.run, fn.__name__, wrapped) - - return wrapped - - -@fixture -def sync_run(run): - """Blocking run for logging data""" - return SyncRun(run) + def log(self, *args, **kwargs): + result = super().log(*args, **kwargs) + self.wait_for_processing() + return result @fixture(scope="module") -def run(project): - """Plain neptune_scale.Run instance. We're scoping it to "module", as it seems to be a - good compromise, mostly because of execution time.""" +def run_init_kwargs(project): + """Arguments to initialize a neptune_scale.Run instance""" # TODO: if a test fails the run could be left in an indefinite state # Maybe we should just have it scoped 'function' and require passing @@ -123,12 +97,26 @@ def run(project): kwargs["run_id"] = kwargs["family"] = run_id - run = Run(**kwargs) + return kwargs + + +@fixture(scope="module") +def run(project, run_init_kwargs): + """Plain neptune_scale.Run instance. We're scoping it to "module", as it seems to be a + good compromise, mostly because of execution time.""" + + run = Run(**run_init_kwargs) run.log_configs({"test_start_time": datetime.now(timezone.utc)}) return run +@fixture(scope="module") +def sync_run(project, run_init_kwargs): + """Blocking run for logging data""" + return SyncRun(**run_init_kwargs) + + @fixture def ro_run(run, project): """ReadOnlyRun pointing to the same run as the neptune_scale.Run""" diff --git a/tests/e2e/test_dataframe_values.py b/tests/e2e/test_dataframe_values.py index f47cf00..2a36709 100644 --- a/tests/e2e/test_dataframe_values.py +++ b/tests/e2e/test_dataframe_values.py @@ -1,7 +1,10 @@ +import os import re import pytest +NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_FIXED_PROJECT") + @pytest.mark.parametrize("limit", [1, 2, 6, 12, 1000]) def test__all_runs_limit(project, all_run_ids, all_experiment_ids, sys_columns, limit): diff --git a/tests/e2e/test_run_filtering.py b/tests/e2e/test_run_filtering.py index 5fd7a08..12643fe 100644 --- a/tests/e2e/test_run_filtering.py +++ b/tests/e2e/test_run_filtering.py @@ -1,9 +1,13 @@ +import os + import pytest # # Tests for filtering runs by various attributes # +NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_FIXED_PROJECT") + def test__runs_no_filter(project, all_run_ids, all_experiment_ids, sys_columns): """Requesting all runs without any filter should return all runs and experiments""" diff --git a/tests/e2e/test_run_list.py b/tests/e2e/test_run_list.py index b1035c3..99fe5a2 100644 --- a/tests/e2e/test_run_list.py +++ b/tests/e2e/test_run_list.py @@ -1,3 +1,8 @@ +import os + +NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_FIXED_PROJECT") + + def test__list_runs(project, all_run_ids, all_experiment_ids, sys_columns_set): result = list(project.list_runs()) ids = all_run_ids + all_experiment_ids From 07bda7fdf065fbc0bb6ad8a259821bd4889d426d Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 24 Oct 2024 12:33:52 +0200 Subject: [PATCH 6/9] Exit with non-zero code on cleanup errors --- .github/scripts/run-e2e-tests.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/run-e2e-tests.sh b/.github/scripts/run-e2e-tests.sh index 2b45a37..754632b 100644 --- a/.github/scripts/run-e2e-tests.sh +++ b/.github/scripts/run-e2e-tests.sh @@ -21,7 +21,6 @@ EXIT_CODE=-1 PROJECT="pye2e-fetcher-$(date +%Y-%m-%d_%H-%M-%S)-$RANDOM" cleanup() { - set +e echo "Cleaning up..." echo "Deleting project $NEPTUNE_WORKSPACE/$PROJECT" From 078a0cc45af698bbc4727d9f715dd32492504812 Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 24 Oct 2024 12:27:11 +0200 Subject: [PATCH 7/9] `trap ERR` in `run-e2e-tests.sh` --- .github/scripts/run-e2e-tests.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/scripts/run-e2e-tests.sh b/.github/scripts/run-e2e-tests.sh index 754632b..9244d76 100644 --- a/.github/scripts/run-e2e-tests.sh +++ b/.github/scripts/run-e2e-tests.sh @@ -21,6 +21,9 @@ EXIT_CODE=-1 PROJECT="pye2e-fetcher-$(date +%Y-%m-%d_%H-%M-%S)-$RANDOM" cleanup() { + # Don't fail tests if cleanup fails + set +e + echo "Cleaning up..." echo "Deleting project $NEPTUNE_WORKSPACE/$PROJECT" @@ -31,7 +34,7 @@ cleanup() { } # Make sure cleanup is called at exit -trap cleanup SIGINT SIGTERM EXIT +trap cleanup SIGINT SIGTERM EXIT ERR run_tests() { export NEPTUNE_PROJECT="$NEPTUNE_WORKSPACE/$PROJECT" From da3e2747cf1093662d6c9b6f7cfeb7e742e2f361 Mon Sep 17 00:00:00 2001 From: Krzysztof Godlewski Date: Thu, 24 Oct 2024 15:21:39 +0200 Subject: [PATCH 8/9] Add another test for ReadOnlyRun --- tests/e2e/test_read_only_run.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/e2e/test_read_only_run.py b/tests/e2e/test_read_only_run.py index be19316..6e9fa24 100644 --- a/tests/e2e/test_read_only_run.py +++ b/tests/e2e/test_read_only_run.py @@ -7,6 +7,8 @@ timezone, ) +from neptune_fetcher import ReadOnlyRun + NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_PROJECT") @@ -14,6 +16,12 @@ def unique_path(prefix): return f"{prefix}__{datetime.now(timezone.utc).isoformat('-', 'seconds')}__{str(uuid.uuid4())[-4:]}" +def refresh(ro_run: ReadOnlyRun): + """Create a new ReadOnlyRun instance with the same project and custom_id, + which is basically a "refresh" operation""" + return ReadOnlyRun(read_only_project=ro_run.project, custom_id=ro_run["sys/custom_run_id"].fetch()) + + def random_series(length=10, start_step=0): """Return a 2-tuple of step and value lists, both of length `length`""" assert length > 0 @@ -85,7 +93,7 @@ def test_series_no_prefetch(run, ro_run): assert df["value"].tolist() == values -def test_series_with_prefetch(run, ro_run): +def test_single_series_with_prefetch(run, ro_run): path = unique_path("test_series/series_with_prefetch") steps, values = random_series() @@ -95,13 +103,31 @@ def test_series_with_prefetch(run, ro_run): run.wait_for_processing() - ro_run.prefetch_series_values([path]) + ro_run.prefetch_series_values([path], use_threads=True) df = ro_run[path].fetch_values() assert df["step"].tolist() == steps assert df["value"].tolist() == values +def test_multiple_series_with_prefetch(run, ro_run): + path_base = unique_path("test_series/many_series_with_prefetch") + data = {f"{path_base}-{i}": i for i in range(20)} + + run.log_metrics(data, step=1) + run.wait_for_processing() + + ro_run = refresh(ro_run) + paths = [p for p in ro_run.field_names if p.startswith(path_base)] + assert len(paths) == len(data), "Not all data was logged" + + ro_run.prefetch_series_values(paths, use_threads=True) + for path in paths: + df = ro_run[path].fetch_values() + assert df["step"].tolist() == [1] + assert df["value"].tolist() == [data[path]] + + def test_series_fetch_and_append(run, ro_run): """Fetch a series, then append, then fetch again -- the new data points should be there""" From f6fd45d5f91fb7b44b5caccb3a8912c294f17f55 Mon Sep 17 00:00:00 2001 From: Patryk Gala Date: Fri, 25 Oct 2024 11:45:04 +0200 Subject: [PATCH 9/9] chore: fix --- tests/populate_projects.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/populate_projects.py b/tests/populate_projects.py index 08513f1..9ea4684 100644 --- a/tests/populate_projects.py +++ b/tests/populate_projects.py @@ -55,8 +55,7 @@ def populate_run(run, run_id, tags=None): data = {f"metrics/foo{x + 1}": value for x in range(MM_NUM_FIELD_KIND)} data |= {f"metrics/bar{x + 1}": value for x in range(MM_NUM_FIELD_KIND)} data |= {f"metrics/bar{x + 1}-unique-{run_id}": value for x in range(10)} - - run.log_metrics(step, data=data) + run.log_metrics(step=step, data=data) # Last step will have a predetermined value step += 1 @@ -64,7 +63,7 @@ def populate_run(run, run_id, tags=None): data |= {f"metrics/bar{x + 1}-unique-{run_id}": x + 1 for x in range(10)} data |= {f"metrics/bar{x + 1}": x + 1 for x in range(MM_NUM_FIELD_KIND)} - run.log_metrics(step, data=data) + run.log_metrics(step=step, data=data) def populate_many_metrics(project):