From 1b8c14539520001bb25e62e52d4ba88fdf0ec7f9 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Mon, 20 Oct 2025 18:54:58 -0700 Subject: [PATCH 01/16] feat(python): Add context manager for creating TestReports. --- python/lib/sift_client/_tests/conftest.py | 2 +- .../sift_client/_tests/resources/test_tags.py | 38 +++- .../{test_results.py => test_test_results.py} | 0 .../lib/sift_client/sift_types/test_report.py | 17 +- .../sift_client/util/test_results/__init__.py | 31 +++ .../util/test_results/context_manager.py | 211 ++++++++++++++++++ 6 files changed, 286 insertions(+), 13 deletions(-) rename python/lib/sift_client/_tests/resources/{test_results.py => test_test_results.py} (100%) create mode 100644 python/lib/sift_client/util/test_results/__init__.py create mode 100644 python/lib/sift_client/util/test_results/context_manager.py diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index 397848d7e..070109222 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -25,7 +25,7 @@ def sift_client() -> SiftClient: api_key=api_key, grpc_url=grpc_url, rest_url=rest_url, - use_ssl=True, + use_ssl=False, ) ) diff --git a/python/lib/sift_client/_tests/resources/test_tags.py b/python/lib/sift_client/_tests/resources/test_tags.py index 0e1269b5f..f51368f6d 100644 --- a/python/lib/sift_client/_tests/resources/test_tags.py +++ b/python/lib/sift_client/_tests/resources/test_tags.py @@ -8,12 +8,15 @@ """ import re +import sys from datetime import datetime, timezone import pytest +from sift_client.client import SiftClient from sift_client.resources import TagsAPI, TagsAPIAsync from sift_client.sift_types import Tag +from sift_client.util.test_results import ReportContext pytestmark = pytest.mark.integration @@ -47,21 +50,38 @@ def test_tags(sift_client, test_timestamp_str): # Would like to archive the tags, but this is not supported by the API +@pytest.fixture(scope="session") +def report_context(sift_client: SiftClient): + report_context = ReportContext.create( + sift_client, + name=f"Test Tags {datetime.now(timezone.utc).isoformat()}", + test_system_name="sift_client CI", + ) + yield report_context + update = { + "end_time": datetime.now(timezone.utc), + } + if report_context.any_failures: + update["status"] = 3 # TestStatus.FAILED + report_context.report.update(update) + class TestTags: """Tests for the Tags API.""" - def test_basic_list(self, sift_client, test_tags, test_timestamp_str): + def test_basic_list(self, sift_client, test_tags, test_timestamp_str, report_context): """Test basic tag listing functionality.""" - tags = sift_client.tags.list_(limit=5) + with report_context.new_step(name="Test basic list") as step: + tags = sift_client.tags.list_(limit=5) - # Verify we get a list - assert isinstance(tags, list) + # Verify we get a list + assert isinstance(tags, list) - # If we have tags, verify their structure - tag = tags[0] - assert isinstance(tag, Tag) - assert tag.id_ is not None - assert tag.name is not None + # If we have tags, verify their structure + tag = tags[0] + assert isinstance(tag, Tag) + assert tag.id_ is not None + assert tag.name is not None + step.measure(name="tag.id_", value=tag.id_, bounds=tag.id_) def test_list_with_name_filter(self, sift_client, test_tags, test_timestamp_str): """Test tag listing with name filtering.""" diff --git a/python/lib/sift_client/_tests/resources/test_results.py b/python/lib/sift_client/_tests/resources/test_test_results.py similarity index 100% rename from python/lib/sift_client/_tests/resources/test_results.py rename to python/lib/sift_client/_tests/resources/test_test_results.py diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py index 7621bdf37..d7c11da87 100644 --- a/python/lib/sift_client/sift_types/test_report.py +++ b/python/lib/sift_client/sift_types/test_report.py @@ -336,16 +336,27 @@ class TestMeasurementBase(ModelCreateUpdateBase): unit: str | None = None numeric_bounds: NumericBounds | None = None string_expected_value: str | None = None + measurement_type: TestMeasurementType | None = None + def _get_proto_class(self) -> type[TestMeasurementProto]: return TestMeasurementProto + def _resolve_measurement_type(self) -> TestMeasurementType: + if self.numeric_value is not None: + return TestMeasurementType.DOUBLE + elif self.string_value is not None: + return TestMeasurementType.STRING + elif self.boolean_value is not None: + return TestMeasurementType.BOOLEAN + else: + raise ValueError("No measurement value provided") + class TestMeasurementUpdate(TestMeasurementBase, ModelUpdate[TestMeasurementProto]): """Update model for TestMeasurement.""" name: str | None = None - measurement_type: int | None = None passed: bool | None = None timestamp: datetime | None = None @@ -364,16 +375,16 @@ def _add_resource_id_to_proto(self, proto_msg: TestMeasurementProto): class TestMeasurementCreate(TestMeasurementBase, ModelCreate[TestMeasurementProto]): """Create model for TestMeasurement.""" - measurement_type: TestMeasurementType name: str test_step_id: str passed: bool timestamp: datetime + unit: str | None = None def to_proto(self) -> TestMeasurementProto: """Convert to protobuf message with custom logic.""" proto = TestMeasurementProto( - measurement_type=self.measurement_type.value, # type: ignore + measurement_type=self.measurement_type.value if self.measurement_type else self._resolve_measurement_type().value, # type: ignore name=self.name, test_step_id=self.test_step_id, passed=self.passed, diff --git a/python/lib/sift_client/util/test_results/__init__.py b/python/lib/sift_client/util/test_results/__init__.py new file mode 100644 index 000000000..9904586fa --- /dev/null +++ b/python/lib/sift_client/util/test_results/__init__.py @@ -0,0 +1,31 @@ +"""Test Results Utilities. + +This module provides utilities for working with test results. + +# Context Managers +- `NewStep` - Context manager to create a new step in a test report. +- `ReportContext` - Context for a new TestReport. Mostly serves as a store to communicate between step context managers since they can be nested or siblings. + +### Examples + +```python +rc = ReportContext.create(client, name="Example Report", description="Example Report") + +with rc.new_step(name="Setup") as step: + controller_setup(step) +with rc.new_step(name="Example Step", description=desc) as parent_step: + cmd_interface.cmd("ec1", "rtv.cmd", 75.0) + sleep(0.01) + + with rc.new_step(name="Substep 1", description="Measure position") as step: + ec = "ec1" + pos_channel = "rtv.pos" + pos = tlm.read(ec, pos_channel) + success = step.measure(pos, name=f"{ec}.{pos_channel}", bounds=(min=74.9, max=75.1)) + return success # This is optional for other uses, but the step and its parents will be updated correctly i.e. failed if the measurement fails. +``` +""" + +from .context_manager import NewStep, ReportContext + +__all__ = ["NewStep", "ReportContext"] diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py new file mode 100644 index 000000000..e88ba25e8 --- /dev/null +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -0,0 +1,211 @@ +from __future__ import annotations + +from contextlib import AbstractContextManager +import os +from datetime import datetime, timezone +from typing import TYPE_CHECKING, ClassVar + +from sift_client.sift_types.test_report import ( + NumericBounds, + TestMeasurementCreate, + TestReport, + TestReportCreate, + TestStatus, + TestStep, + TestStepCreate, + TestStepType, +) + +if TYPE_CHECKING: + from sift_client.client import SiftClient + + +class ReportContext: + """Context for a new TestReport. Mostly serves as a store to communicate between step context managers since they can be nested or siblings.""" + + report: TestReport + step_is_open: bool = False + step_stack: ClassVar[list[(int, TestStep)]] = [] + open_step_results: ClassVar[dict[str, bool]] = {} + any_failures: bool = False + + def __init__(self, report: TestReport): + """Initialize a new report context. + + Args: + report: The report to create the context for. + """ + self.report = report + + @classmethod + def create( + cls, client: SiftClient, name: str, test_system_name: str, test_case: str | None = None + ) -> ReportContext: + """Create a new report context.""" + test_case = test_case if test_case else os.path.basename(__file__) + + create = TestReportCreate( + name=name, + test_system_name=test_system_name, + test_case=test_case, + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + status=TestStatus.IN_PROGRESS, + ) + report = client.test_results.create(create) + return cls(report) + + def new_step(self, name: str, description: str | None = None) -> NewStep: + """Create a new step in the report context.""" + return NewStep(self, name=name, description=description) + + +class NewStep(AbstractContextManager): + """Context manager to create a new step in a test report.""" + + report_context: ReportContext + client: SiftClient + current_step: TestStep | None = None + name: str | None = None + step_path: str | None = None + description: str | None = None + parent_step: TestStep | None = None + + def __init__( + self, + report_context: ReportContext, + name: str | None = None, + description: str | None = None, + ): + """Initialize a new step context. + + Args: + report_context: The report context to create the step in. + name: The name of the step. + description: The description of the step. + """ + self.report_context = report_context + self.client = report_context.report.client + self.name = name + self.description = description + self._update_step_stack() + + + def __enter__(self): + """Enter the context manager to create a new step. + + returns: The current step. + """ + self.current_step = self.client.test_results.create_step( + TestStepCreate( + test_report_id=self.report_context.report.id_, + name=self.name, + step_type=TestStepType.ACTION, + step_path=self.step_path, + status=TestStatus.IN_PROGRESS, + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + description=self.description, + parent_step_id=self.parent_step.id_ if self.parent_step else None, + ) + ) + self.report_context.step_stack.append((0, self.current_step)) + # Create an entry in the open step results for this step that can be modified by substeps/measurements. + self.report_context.open_step_results[self.step_path] = True + return self + + def __exit__(self, exc_type, exc_value, traceback): + result = self._resolve_and_propagate_result() + + # Mark the step as completed + self.current_step.update( + { + "status": TestStatus.PASSED if result else TestStatus.FAILED, + "end_time": datetime.now(timezone.utc), + } + ) + # Update the last step to the parent. + _, stack_top = self.report_context.step_stack.pop() + if stack_top.id_ != self.current_step.id_: + raise ValueError( + "The current step is not the top of the stack. This should never happen." + ) + + def _update_step_stack(self): + """Update the step stack with the new step number if there is an existing stack.""" + step_number, parent_step = ( + self.report_context.step_stack[-1] if self.report_context.step_stack else (0, None) + ) + step_number += 1 + prefix = f"{parent_step.step_path}." if parent_step else "" + if parent_step: + # Increment the step number in the stack. + _, parent_step = self.report_context.step_stack.pop() + self.report_context.step_stack.append((step_number, parent_step)) + self.step_path = f"{prefix}{step_number}" + + def _resolve_and_propagate_result(self) -> bool: + """Get the result of the step from the report context and update the report context for the parent step if this step failed.""" + result = self.report_context.open_step_results.get(self.current_step.step_path, True) + if self.current_step.status != TestStatus.IN_PROGRESS: + # The step was not manually completed so use that. + result = self.current_step.status == TestStatus.PASSED + + # Update the parent step results if this step failed (true by default so no need to do anything if we didn't fail). + if self.parent_step and not result: + parent_result = self.report_context.open_step_results.get( + self.parent_step.step_path, True + ) + self.report_context.open_step_results[self.parent_step.step_path] = ( + parent_result and result + ) + self.report_context.any_failures = True + + # TODO: Cleanup the open step results for this step? + + return result + + def measure( + self, *, name: str, value: float | str | bool, bounds: NumericBounds | str | None = None + ) -> bool: + """Measure a value and return the result. + + returns: The measurement object. + """ + + create = TestMeasurementCreate( + test_step_id=self.current_step.id_, + name=name, + passed=True, + timestamp=datetime.now(timezone.utc), + ) + + if bounds is not None: + if isinstance(bounds, str): + if not isinstance(value, str): + raise ValueError("Value must be a string if bounds provided is a string") + create.string_expected_value = bounds + create.string_value = value + create.passed = value == bounds + elif isinstance(bounds, NumericBounds): + if not (isinstance(value, float) or isinstance(value, int)): + raise ValueError( + "Value must be a float or int if bounds provided are numeric bounds" + ) + create.numeric_bounds = bounds + create.numeric_value = float(value) + if create.numeric_value.min is not None: + create.passed = ( + create.passed and create.numeric_value.min <= create.numeric_value + ) + if create.numeric_value.max is not None: + create.passed = ( + create.passed and create.numeric_value.max >= create.numeric_value + ) + + if not create.passed: + # Propogate failures to the report context so the step will be marked correctly when it exists context. + self.report_context.open_step_results[self.current_step.step_path] = False + + measurement = self.client.test_results.create_measurement(create) + return measurement.passed From 5fe139cf4caa0cc99863c8ce076e6b01de7cbaa6 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Wed, 22 Oct 2025 17:38:54 -0700 Subject: [PATCH 02/16] Pytest fixtures auto attaching and creating reports. --- python/lib/sift_client/_tests/conftest.py | 3 + .../sift_client/_tests/resources/test_tags.py | 38 +-- .../_tests/resources/test_test_results.py | 19 +- .../_tests/util/test_test_results_utils.py | 0 .../lib/sift_client/sift_types/test_report.py | 5 +- .../sift_client/util/test_results/__init__.py | 3 +- .../sift_client/util/test_results/bounds.py | 64 ++++++ .../util/test_results/context_manager.py | 216 ++++++++++-------- .../sift_client/util/test_results/pytest.py | 42 ++++ 9 files changed, 262 insertions(+), 128 deletions(-) create mode 100644 python/lib/sift_client/_tests/util/test_test_results_utils.py create mode 100644 python/lib/sift_client/util/test_results/bounds.py create mode 100644 python/lib/sift_client/util/test_results/pytest.py diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index 070109222..3cc38c9e3 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -70,3 +70,6 @@ def ci_pytest_tag(sift_client): tag = sift_client.tags.find_or_create(names=["sift-client-pytest"])[0] assert tag is not None return tag + + +from sift_client.util.test_results.pytest import report_context, step # noqa: F401 diff --git a/python/lib/sift_client/_tests/resources/test_tags.py b/python/lib/sift_client/_tests/resources/test_tags.py index f51368f6d..0e1269b5f 100644 --- a/python/lib/sift_client/_tests/resources/test_tags.py +++ b/python/lib/sift_client/_tests/resources/test_tags.py @@ -8,15 +8,12 @@ """ import re -import sys from datetime import datetime, timezone import pytest -from sift_client.client import SiftClient from sift_client.resources import TagsAPI, TagsAPIAsync from sift_client.sift_types import Tag -from sift_client.util.test_results import ReportContext pytestmark = pytest.mark.integration @@ -50,38 +47,21 @@ def test_tags(sift_client, test_timestamp_str): # Would like to archive the tags, but this is not supported by the API -@pytest.fixture(scope="session") -def report_context(sift_client: SiftClient): - report_context = ReportContext.create( - sift_client, - name=f"Test Tags {datetime.now(timezone.utc).isoformat()}", - test_system_name="sift_client CI", - ) - yield report_context - update = { - "end_time": datetime.now(timezone.utc), - } - if report_context.any_failures: - update["status"] = 3 # TestStatus.FAILED - report_context.report.update(update) - class TestTags: """Tests for the Tags API.""" - def test_basic_list(self, sift_client, test_tags, test_timestamp_str, report_context): + def test_basic_list(self, sift_client, test_tags, test_timestamp_str): """Test basic tag listing functionality.""" - with report_context.new_step(name="Test basic list") as step: - tags = sift_client.tags.list_(limit=5) + tags = sift_client.tags.list_(limit=5) - # Verify we get a list - assert isinstance(tags, list) + # Verify we get a list + assert isinstance(tags, list) - # If we have tags, verify their structure - tag = tags[0] - assert isinstance(tag, Tag) - assert tag.id_ is not None - assert tag.name is not None - step.measure(name="tag.id_", value=tag.id_, bounds=tag.id_) + # If we have tags, verify their structure + tag = tags[0] + assert isinstance(tag, Tag) + assert tag.id_ is not None + assert tag.name is not None def test_list_with_name_filter(self, sift_client, test_tags, test_timestamp_str): """Test tag listing with name filtering.""" diff --git a/python/lib/sift_client/_tests/resources/test_test_results.py b/python/lib/sift_client/_tests/resources/test_test_results.py index b09b9b839..d3695f6d9 100644 --- a/python/lib/sift_client/_tests/resources/test_test_results.py +++ b/python/lib/sift_client/_tests/resources/test_test_results.py @@ -1,5 +1,6 @@ from __future__ import annotations +import socket from datetime import datetime, timedelta, timezone from pathlib import Path from typing import ClassVar @@ -23,8 +24,14 @@ TestStepType, ) +# from sift_client.util.test_results.pytest import report_context, step +from sift_client.util.test_results.context_manager import NewStep # noqa: F401 + pytestmark = pytest.mark.integration +case_name = Path(__file__).stem +test_system_name = socket.gethostname() + def test_client_binding(sift_client): assert sift_client.test_results @@ -38,7 +45,8 @@ class TestResultsTest: test_steps: ClassVar[dict[str, TestStep]] = {} test_measurements: ClassVar[dict[str, TestMeasurement]] = {} - def test_create_test_report(self, sift_client): + def test_create_test_report(self, sift_client, step): + print("STEP", step) # Create a test report simulated_time = datetime.now(timezone.utc) test_report = sift_client.test_results.create( @@ -53,8 +61,13 @@ def test_create_test_report(self, sift_client): ) assert test_report.id_ is not None self.test_reports["basic_test_report"] = test_report + step.measure(name="test_report_created", value=True) + with step.substep(name="nested step") as nested_step: + nested_step.measure(name="nested_step_created", value=True) + with nested_step.substep(name="nested nested step") as nested_nested_step: + nested_nested_step.measure(name="nested_nested_step_created", value=True) - def test_create_test_steps(self, sift_client): + def test_create_test_steps(self, sift_client, step): test_report = self.test_reports.get("basic_test_report") if not test_report: pytest.skip("Need to create a test report first") @@ -74,7 +87,7 @@ def test_create_test_steps(self, sift_client): ), ) simulated_time = simulated_time + timedelta(seconds=10.1) - + step.measure(name="step1_created", value=True) # Create a step using a dict step1_1 = sift_client.test_results.create_step( { diff --git a/python/lib/sift_client/_tests/util/test_test_results_utils.py b/python/lib/sift_client/_tests/util/test_test_results_utils.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py index d7c11da87..d180c8de3 100644 --- a/python/lib/sift_client/sift_types/test_report.py +++ b/python/lib/sift_client/sift_types/test_report.py @@ -338,7 +338,6 @@ class TestMeasurementBase(ModelCreateUpdateBase): string_expected_value: str | None = None measurement_type: TestMeasurementType | None = None - def _get_proto_class(self) -> type[TestMeasurementProto]: return TestMeasurementProto @@ -384,7 +383,9 @@ class TestMeasurementCreate(TestMeasurementBase, ModelCreate[TestMeasurementProt def to_proto(self) -> TestMeasurementProto: """Convert to protobuf message with custom logic.""" proto = TestMeasurementProto( - measurement_type=self.measurement_type.value if self.measurement_type else self._resolve_measurement_type().value, # type: ignore + measurement_type=self.measurement_type.value + if self.measurement_type + else self._resolve_measurement_type().value, # type: ignore name=self.name, test_step_id=self.test_step_id, passed=self.passed, diff --git a/python/lib/sift_client/util/test_results/__init__.py b/python/lib/sift_client/util/test_results/__init__.py index 9904586fa..9734fa1ee 100644 --- a/python/lib/sift_client/util/test_results/__init__.py +++ b/python/lib/sift_client/util/test_results/__init__.py @@ -27,5 +27,6 @@ """ from .context_manager import NewStep, ReportContext +from .pytest import report_context -__all__ = ["NewStep", "ReportContext"] +__all__ = ["NewStep", "ReportContext", "report_context"] diff --git a/python/lib/sift_client/util/test_results/bounds.py b/python/lib/sift_client/util/test_results/bounds.py new file mode 100644 index 000000000..e8fa97903 --- /dev/null +++ b/python/lib/sift_client/util/test_results/bounds.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from sift_client.sift_types.test_report import ( + NumericBounds, + TestMeasurement, + TestMeasurementCreate, + TestMeasurementUpdate, +) + + +def assign_value_to_measurement( + measurement: TestMeasurement | TestMeasurementCreate | TestMeasurementUpdate, + value: float | str | bool, +) -> None: + """Assign the resolved value type to a measurement.""" + if isinstance(value, float): + measurement.numeric_value = value + elif isinstance(value, str): + measurement.string_value = value + elif isinstance(value, bool): + measurement.boolean_value = value + else: + raise ValueError("Invalid value type") + + +def evaluate_measurement_bounds( + measurement: TestMeasurement | TestMeasurementCreate | TestMeasurementUpdate, + value: float | str | bool, + bounds: dict[str, float] | NumericBounds | str | None, +) -> bool: + """Update a measurement with the resolved bounds type and result of evaluating the given value against those bounds. + + Args: + measurement: The measurement to update. + value: The value to evaluate the bounds of. + bounds: The bounds to evaluate the value against. + + Returns: + True if the value is within the bounds, False otherwise. + """ + assign_value_to_measurement(measurement, value) + if bounds is not None: + if isinstance(bounds, dict): + bounds = NumericBounds(min=bounds.get("min"), max=bounds.get("max")) + if isinstance(bounds, str): + if not (isinstance(value, str) or isinstance(value, bool)): + raise ValueError("Value must be a string if bounds provided is a string") + measurement.string_expected_value = bounds + if isinstance(value, bool): + measurement.passed = str(value).lower() == str(bounds).lower() + else: + measurement.passed = value == bounds + elif isinstance(bounds, NumericBounds): + measurement.numeric_bounds = bounds + float_value = float(value) + if measurement.numeric_bounds.min is not None: + measurement.passed = ( + measurement.passed and measurement.numeric_bounds.min <= float_value + ) + if measurement.numeric_bounds.max is not None: + measurement.passed = ( + measurement.passed and measurement.numeric_bounds.max >= float_value + ) + return measurement.passed diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index e88ba25e8..52d4fc73a 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -1,9 +1,11 @@ from __future__ import annotations -from contextlib import AbstractContextManager +import getpass import os +import socket +from contextlib import AbstractContextManager from datetime import datetime, timezone -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING from sift_client.sift_types.test_report import ( NumericBounds, @@ -15,19 +17,24 @@ TestStepCreate, TestStepType, ) +from sift_client.util.test_results.bounds import ( + assign_value_to_measurement, + evaluate_measurement_bounds, +) if TYPE_CHECKING: from sift_client.client import SiftClient class ReportContext: - """Context for a new TestReport. Mostly serves as a store to communicate between step context managers since they can be nested or siblings.""" + """Context for a new TestReport. Is not itself a context manager but serves as a store to communicate between step context managers since they can be nested or siblings.""" report: TestReport - step_is_open: bool = False - step_stack: ClassVar[list[(int, TestStep)]] = [] - open_step_results: ClassVar[dict[str, bool]] = {} - any_failures: bool = False + step_is_open: bool + step_stack: list[TestStep] + step_number_at_depth: dict[int, int] + open_step_results: dict[str, bool] + any_failures: bool def __init__(self, report: TestReport): """Initialize a new report context. @@ -36,14 +43,37 @@ def __init__(self, report: TestReport): report: The report to create the context for. """ self.report = report + self.step_is_open = False + self.step_stack = [] + self.step_number_at_depth = {} + self.open_step_results = {} + self.any_failures = False @classmethod def create( - cls, client: SiftClient, name: str, test_system_name: str, test_case: str | None = None + cls, + client: SiftClient, + name: str, + test_system_name: str | None = None, + system_operator: str | None = None, + test_case: str | None = None, ) -> ReportContext: - """Create a new report context.""" - test_case = test_case if test_case else os.path.basename(__file__) + """Create a new report context. + Args: + client: The Sift client to use to create the report. + name: The name of the report. + test_system_name: The name of the test system. Will default to the hostname if not provided. + system_operator: The operator of the test system. Will default to the current user if not provided. + test_case: The name of the test case. Will default to the basename of the file containing the test if not provided. + + Returns: + The new report context. + """ + print(f"Creating new report context: {name} {test_system_name} {test_case}") + test_case = test_case if test_case else os.path.basename(__file__) + test_system_name = test_system_name if test_system_name else socket.gethostname() + system_operator = system_operator if system_operator else getpass.getuser() create = TestReportCreate( name=name, test_system_name=test_system_name, @@ -51,14 +81,70 @@ def create( start_time=datetime.now(timezone.utc), end_time=datetime.now(timezone.utc), status=TestStatus.IN_PROGRESS, + system_operator=system_operator, ) report = client.test_results.create(create) return cls(report) def new_step(self, name: str, description: str | None = None) -> NewStep: - """Create a new step in the report context.""" + """Alias to return a new step context manager from this report context. Use create_step for actually creating a TestStep in the current context.""" return NewStep(self, name=name, description=description) + def get_next_step_path(self) -> str: + """Get the next step path for the current depth.""" + top_step = self.step_stack[-1] if self.step_stack else None + step_path = top_step.step_path if top_step else "" + next_step_number = self.step_number_at_depth.get(len(self.step_stack), 0) + 1 + prefix = f"{step_path}." if step_path else "" + return f"{prefix}{next_step_number}" + + def create_step(self, name: str, description: str | None = None) -> TestStep: + """Create a new step in the report context.""" + step_path = self.get_next_step_path() + parent_step = self.step_stack[-1] if self.step_stack else None + + step = self.report.client.test_results.create_step( + TestStepCreate( + test_report_id=self.report.id_, + name=name, + step_type=TestStepType.ACTION, + step_path=step_path, + status=TestStatus.IN_PROGRESS, + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + description=description, + parent_step_id=parent_step.id_ if parent_step else None, + ) + ) + + # Update the step tracking structures. + self.step_number_at_depth[len(self.step_stack)] = ( + self.step_number_at_depth.get(len(self.step_stack), 0) + 1 + ) + self.step_stack.append(step) + self.open_step_results[step.step_path] = True + + return step + + def resolve_and_propagate_step_result( + self, step: TestStep, parent_step: TestStep | None = None + ) -> bool: + """Resolve the result of a step and propagate the result to the parent step if it failed.""" + result = self.open_step_results.get(step.step_path, True) + if step.status != TestStatus.IN_PROGRESS: + # The step was not manually completed so use that. + result = step.status == TestStatus.PASSED + + # Update the parent step results if this step failed (true by default so no need to do anything if we didn't fail). + if not result: + self.any_failures = True + if parent_step: + self.open_step_results[parent_step.step_path] = False + + # Cleanup the open step results for this step. + self.open_step_results.pop(step.step_path) + return result + class NewStep(AbstractContextManager): """Context manager to create a new step in a test report.""" @@ -67,7 +153,6 @@ class NewStep(AbstractContextManager): client: SiftClient current_step: TestStep | None = None name: str | None = None - step_path: str | None = None description: str | None = None parent_step: TestStep | None = None @@ -86,36 +171,24 @@ def __init__( """ self.report_context = report_context self.client = report_context.report.client - self.name = name self.description = description - self._update_step_stack() - + self.name = name def __enter__(self): """Enter the context manager to create a new step. returns: The current step. """ - self.current_step = self.client.test_results.create_step( - TestStepCreate( - test_report_id=self.report_context.report.id_, - name=self.name, - step_type=TestStepType.ACTION, - step_path=self.step_path, - status=TestStatus.IN_PROGRESS, - start_time=datetime.now(timezone.utc), - end_time=datetime.now(timezone.utc), - description=self.description, - parent_step_id=self.parent_step.id_ if self.parent_step else None, - ) - ) - self.report_context.step_stack.append((0, self.current_step)) - # Create an entry in the open step results for this step that can be modified by substeps/measurements. - self.report_context.open_step_results[self.step_path] = True + print(f"Creating step {self.name} for report {self.report_context.report.id_}") + self.current_step = self.report_context.create_step(self.name, self.description) + self.name = self.current_step.name + return self def __exit__(self, exc_type, exc_value, traceback): - result = self._resolve_and_propagate_result() + result = self.report_context.resolve_and_propagate_step_result( + self.current_step, self.parent_step + ) # Mark the step as completed self.current_step.update( @@ -124,88 +197,45 @@ def __exit__(self, exc_type, exc_value, traceback): "end_time": datetime.now(timezone.utc), } ) + print( + "Leaving step", + self.current_step.step_path, + self.current_step.name, + self.current_step.test_report_id, + ) # Update the last step to the parent. - _, stack_top = self.report_context.step_stack.pop() + stack_top = self.report_context.step_stack.pop() if stack_top.id_ != self.current_step.id_: raise ValueError( "The current step is not the top of the stack. This should never happen." ) - def _update_step_stack(self): - """Update the step stack with the new step number if there is an existing stack.""" - step_number, parent_step = ( - self.report_context.step_stack[-1] if self.report_context.step_stack else (0, None) - ) - step_number += 1 - prefix = f"{parent_step.step_path}." if parent_step else "" - if parent_step: - # Increment the step number in the stack. - _, parent_step = self.report_context.step_stack.pop() - self.report_context.step_stack.append((step_number, parent_step)) - self.step_path = f"{prefix}{step_number}" - - def _resolve_and_propagate_result(self) -> bool: - """Get the result of the step from the report context and update the report context for the parent step if this step failed.""" - result = self.report_context.open_step_results.get(self.current_step.step_path, True) - if self.current_step.status != TestStatus.IN_PROGRESS: - # The step was not manually completed so use that. - result = self.current_step.status == TestStatus.PASSED - - # Update the parent step results if this step failed (true by default so no need to do anything if we didn't fail). - if self.parent_step and not result: - parent_result = self.report_context.open_step_results.get( - self.parent_step.step_path, True - ) - self.report_context.open_step_results[self.parent_step.step_path] = ( - parent_result and result - ) - self.report_context.any_failures = True - - # TODO: Cleanup the open step results for this step? - - return result - def measure( - self, *, name: str, value: float | str | bool, bounds: NumericBounds | str | None = None + self, + *, + name: str, + value: float | str | bool, + bounds: dict[str, float] | NumericBounds | str | None = None, ) -> bool: """Measure a value and return the result. returns: The measurement object. """ - create = TestMeasurementCreate( test_step_id=self.current_step.id_, name=name, passed=True, timestamp=datetime.now(timezone.utc), ) - - if bounds is not None: - if isinstance(bounds, str): - if not isinstance(value, str): - raise ValueError("Value must be a string if bounds provided is a string") - create.string_expected_value = bounds - create.string_value = value - create.passed = value == bounds - elif isinstance(bounds, NumericBounds): - if not (isinstance(value, float) or isinstance(value, int)): - raise ValueError( - "Value must be a float or int if bounds provided are numeric bounds" - ) - create.numeric_bounds = bounds - create.numeric_value = float(value) - if create.numeric_value.min is not None: - create.passed = ( - create.passed and create.numeric_value.min <= create.numeric_value - ) - if create.numeric_value.max is not None: - create.passed = ( - create.passed and create.numeric_value.max >= create.numeric_value - ) + evaluate_measurement_bounds(create, value, bounds) + measurement = self.client.test_results.create_measurement(create) if not create.passed: # Propogate failures to the report context so the step will be marked correctly when it exists context. self.report_context.open_step_results[self.current_step.step_path] = False - measurement = self.client.test_results.create_measurement(create) return measurement.passed + + def substep(self, name: str, description: str | None = None) -> NewStep: + """Alias to return a new step context manager from the current step. The ReportContext will manage nesting of steps.""" + return self.report_context.new_step(name=name, description=description) diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py new file mode 100644 index 000000000..6180aa8f9 --- /dev/null +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from typing import TYPE_CHECKING + +import pytest + +from sift_client.util.test_results import ReportContext + +if TYPE_CHECKING: + from sift_client.client import SiftClient + + +# TODO FIGURE OUT HOW TO EXPORT THIS BETTER +@pytest.fixture(scope="module", autouse=True) +def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> ReportContext: + """Create a report context for the session.""" + test_case = str(request.node.name) + print(f"report_context fixture: {test_case} {request.node.name}") + context = ReportContext.create( + sift_client, + name=f"{test_case} {datetime.now(timezone.utc).isoformat()}", + test_case=request.node.name, + ) + yield context + print("test_case in fixture after yield", request.node.name) + update = { + "end_time": datetime.now(timezone.utc), + } + if context.any_failures: + update["status"] = 3 # TestStatus.FAILED + context.report.update(update) + + +@pytest.fixture(autouse=True) +def step(report_context: ReportContext, request: pytest.FixtureRequest): + """Create an outer step for the function.""" + name = str(request.node.name) + print("Step fixture: function name in step", name) + with report_context.new_step(name=name) as new_step: + print("Step fixture: step", new_step) + yield new_step From 094d9ab2761925ccbd6b458b5acb8348e3ca133a Mon Sep 17 00:00:00 2001 From: Ian Later Date: Wed, 22 Oct 2025 18:06:03 -0700 Subject: [PATCH 03/16] Fixup test name/case --- python/lib/sift_client/_tests/conftest.py | 2 +- .../util/test_results/context_manager.py | 16 +++++++++----- .../sift_client/util/test_results/pytest.py | 22 ++++++++++++------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index 3cc38c9e3..e9a8bd460 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -72,4 +72,4 @@ def ci_pytest_tag(sift_client): return tag -from sift_client.util.test_results.pytest import report_context, step # noqa: F401 +from sift_client.util.test_results.pytest import report_context, step, module_substep # noqa: F401 diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index 52d4fc73a..e11114fd4 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -145,6 +145,16 @@ def resolve_and_propagate_step_result( self.open_step_results.pop(step.step_path) return result + def exit_step(self, step: TestStep): + """Exit a step and update the report context.""" + self.step_number_at_depth[len(self.step_stack)] = 0 + stack_top = self.step_stack.pop() + + if stack_top.id_ != step.id_: + raise ValueError( + "The popped step was not the top of the stack. This should never happen." + ) + class NewStep(AbstractContextManager): """Context manager to create a new step in a test report.""" @@ -204,11 +214,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.current_step.test_report_id, ) # Update the last step to the parent. - stack_top = self.report_context.step_stack.pop() - if stack_top.id_ != self.current_step.id_: - raise ValueError( - "The current step is not the top of the stack. This should never happen." - ) + self.report_context.exit_step(self.current_step) def measure( self, diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 6180aa8f9..048aea4f4 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -2,9 +2,9 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING - +import sys import pytest - +from pathlib import Path from sift_client.util.test_results import ReportContext if TYPE_CHECKING: @@ -12,18 +12,16 @@ # TODO FIGURE OUT HOW TO EXPORT THIS BETTER -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="session", autouse=True) def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> ReportContext: """Create a report context for the session.""" - test_case = str(request.node.name) - print(f"report_context fixture: {test_case} {request.node.name}") + test_path = Path(request.config.invocation_params.args[0]) context = ReportContext.create( sift_client, - name=f"{test_case} {datetime.now(timezone.utc).isoformat()}", - test_case=request.node.name, + name=f"{test_path.name} {datetime.now(timezone.utc).isoformat()}", + test_case=str(test_path), ) yield context - print("test_case in fixture after yield", request.node.name) update = { "end_time": datetime.now(timezone.utc), } @@ -40,3 +38,11 @@ def step(report_context: ReportContext, request: pytest.FixtureRequest): with report_context.new_step(name=name) as new_step: print("Step fixture: step", new_step) yield new_step + + +@pytest.fixture(scope="module", autouse=True) +def module_substep(report_context: ReportContext, request: pytest.FixtureRequest): + """Create a step per module.""" + name = str(request.node.name) + with report_context.new_step(name=name) as new_step: + yield new_step From 491669a4791685e0e30846c55281e75412ce2ad2 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Thu, 23 Oct 2025 11:02:00 -0700 Subject: [PATCH 04/16] update test result proto to include run id --- .../sift/test_reports/v1/test_reports.proto | 7 +- .../sift/test_reports/v1/test_reports_pb2.py | 178 ++++++++-------- .../sift/test_reports/v1/test_reports_pb2.pyi | 10 +- python/lib/sift_client/_tests/conftest.py | 2 +- .../lib/sift_client/sift_types/test_report.py | 191 +++++++++--------- .../util/test_results/context_manager.py | 1 - .../sift_client/util/test_results/pytest.py | 8 +- 7 files changed, 202 insertions(+), 195 deletions(-) diff --git a/protos/sift/test_reports/v1/test_reports.proto b/protos/sift/test_reports/v1/test_reports.proto index 478a6d701..16285b3dc 100644 --- a/protos/sift/test_reports/v1/test_reports.proto +++ b/protos/sift/test_reports/v1/test_reports.proto @@ -53,6 +53,9 @@ message TestReport { // Whether the test run is archived (externally exposed) bool is_archived = 13 [(google.api.field_behavior) = OPTIONAL]; + + // The run ID for the test run + string run_id = 14 [(google.api.field_behavior) = OPTIONAL]; } enum TestStatus { @@ -415,7 +418,7 @@ message ListTestReportsRequest { // A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. // Available fields to filter by are `test_report_id`, `status`, `name`, `test_system_name`, // `test_case`, `start_time`, `end_time`, `serial_number`, `created_by_user_id`, `modified_by_user_id`, - // `part_number`, `system_operator`, `archived_date`, and `metadata`. + // `part_number`, `system_operator`, `run_id`, `archived_date`, and `metadata`. // Metadata can be used in filters by using `metadata.{metadata_key_name}` as the field name. // For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). // For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testreport). Optional. @@ -446,7 +449,7 @@ message UpdateTestReportRequest { // The field mask specifying which fields to update. The fields available to be updated are // `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, - // `part_number`, `system_operator`, and `is_archived`. + // `part_number`, `system_operator`, `run_id`, and `is_archived`. google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL]; } diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.py b/python/lib/sift/test_reports/v1/test_reports_pb2.py index b5f5fa31c..7c7aaa2b5 100644 --- a/python/lib/sift/test_reports/v1/test_reports_pb2.py +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.py @@ -21,7 +21,7 @@ from sift.unit.v2 import unit_pb2 as sift_dot_unit_dot_v2_dot_unit__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'sift/test_reports/v1/test_reports.proto\x12\x14sift.test_reports.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x1fsift/metadata/v1/metadata.proto\x1a\x17sift/unit/v2/unit.proto\"\x88\x05\n\nTestReport\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12=\n\x06status\x18\x02 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x08 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\t \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\n \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x44\n\rarchived_date\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x01R\x0c\x61rchivedDate\x12$\n\x0bis_archived\x18\r \x01(\x08\x42\x03\xe0\x41\x01R\nisArchived\"\xaf\x04\n\x08TestStep\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12)\n\x0eparent_step_id\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x0cparentStepId\x12\x17\n\x04name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x03\xe0\x41\x01R\x0b\x64\x65scription\x12\x44\n\tstep_type\x18\x06 \x01(\x0e\x32\".sift.test_reports.v1.TestStepTypeB\x03\xe0\x41\x02R\x08stepType\x12 \n\tstep_path\x18\x07 \x01(\tB\x03\xe0\x41\x02R\x08stepPath\x12=\n\x06status\x18\x08 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12>\n\nstart_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12\x43\n\nerror_info\x18\x0b \x01(\x0b\x32\x1f.sift.test_reports.v1.ErrorInfoB\x03\xe0\x41\x01R\terrorInfo\"Y\n\tErrorInfo\x12\"\n\nerror_code\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\terrorCode\x12(\n\rerror_message\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0c\x65rrorMessage\"\xab\x05\n\x0fTestMeasurement\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\x12Y\n\x10measurement_type\x18\x02 \x01(\x0e\x32).sift.test_reports.v1.TestMeasurementTypeB\x03\xe0\x41\x02R\x0fmeasurementType\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ctest_step_id\x18\x04 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12%\n\rnumeric_value\x18\x06 \x01(\x01H\x00R\x0cnumericValue\x12#\n\x0cstring_value\x18\x07 \x01(\tH\x00R\x0bstringValue\x12%\n\rboolean_value\x18\x08 \x01(\x08H\x00R\x0c\x62ooleanValue\x12+\n\x04unit\x18\t \x01(\x0b\x32\x12.sift.unit.v2.UnitB\x03\xe0\x41\x01R\x04unit\x12L\n\x0enumeric_bounds\x18\n \x01(\x0b\x32#.sift.test_reports.v1.NumericBoundsH\x01R\rnumericBounds\x12I\n\rstring_bounds\x18\x0b \x01(\x0b\x32\".sift.test_reports.v1.StringBoundsH\x01R\x0cstringBounds\x12\x1b\n\x06passed\x18\x0c \x01(\x08\x42\x03\xe0\x41\x02R\x06passed\x12=\n\ttimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\ttimestampB\x07\n\x05valueB\x08\n\x06\x62ounds\"W\n\rNumericBounds\x12\x1a\n\x03min\x18\x01 \x01(\x01\x42\x03\xe0\x41\x01H\x00R\x03min\x88\x01\x01\x12\x1a\n\x03max\x18\x02 \x01(\x01\x42\x03\xe0\x41\x01H\x01R\x03max\x88\x01\x01\x42\x06\n\x04_minB\x06\n\x04_max\":\n\x0cStringBounds\x12*\n\x0e\x65xpected_value\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rexpectedValue\"D\n\x17ImportTestReportRequest\x12)\n\x0eremote_file_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0cremoteFileId\"]\n\x18ImportTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\xfe\x03\n\x17\x43reateTestReportRequest\x12=\n\x06status\x18\x01 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x07 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\x08 \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\t \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\n \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\"]\n\x18\x43reateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"A\n\x14GetTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"Z\n\x15GetTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\x9b\x01\n\x16ListTestReportsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x86\x01\n\x17ListTestReportsResponse\x12\x43\n\x0ctest_reports\x18\x01 \x03(\x0b\x32 .sift.test_reports.v1.TestReportR\x0btestReports\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa3\x01\n\x17UpdateTestReportRequest\x12\x46\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportB\x03\xe0\x41\x02R\ntestReport\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"]\n\x18UpdateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"D\n\x17\x44\x65leteTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"\x1a\n\x18\x44\x65leteTestReportResponse\"Y\n\x15\x43reateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\"U\n\x16\x43reateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\"\x99\x01\n\x14ListTestStepsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"~\n\x15ListTestStepsResponse\x12=\n\ntest_steps\x18\x01 \x03(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\ttestSteps\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x9b\x01\n\x15UpdateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"U\n\x16UpdateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\">\n\x15\x44\x65leteTestStepRequest\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\"\x18\n\x16\x44\x65leteTestStepResponse\"u\n\x1c\x43reateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\"q\n\x1d\x43reateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"x\n\x1d\x43reateTestMeasurementsRequest\x12W\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x10testMeasurements\"\x91\x01\n\x1e\x43reateTestMeasurementsResponse\x12\x41\n\x1ameasurements_created_count\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\x18measurementsCreatedCount\x12,\n\x0fmeasurement_ids\x18\x02 \x03(\tB\x03\xe0\x41\x02R\x0emeasurementIds\"\xa0\x01\n\x1bListTestMeasurementsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x9a\x01\n\x1cListTestMeasurementsResponse\x12R\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x10testMeasurements\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"4\n\x15\x43ountTestStepsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\".\n\x16\x43ountTestStepsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\";\n\x1c\x43ountTestMeasurementsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"5\n\x1d\x43ountTestMeasurementsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\"\xb7\x01\n\x1cUpdateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"q\n\x1dUpdateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"J\n\x1c\x44\x65leteTestMeasurementRequest\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\"\x1f\n\x1d\x44\x65leteTestMeasurementResponse*\xd6\x01\n\nTestStatus\x12\x1b\n\x17TEST_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11TEST_STATUS_DRAFT\x10\x01\x12\x16\n\x12TEST_STATUS_PASSED\x10\x02\x12\x16\n\x12TEST_STATUS_FAILED\x10\x03\x12\x17\n\x13TEST_STATUS_ABORTED\x10\x04\x12\x15\n\x11TEST_STATUS_ERROR\x10\x05\x12\x1b\n\x17TEST_STATUS_IN_PROGRESS\x10\x06\x12\x17\n\x13TEST_STATUS_SKIPPED\x10\x07*\xa1\x01\n\x0cTestStepType\x12\x1e\n\x1aTEST_STEP_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TEST_STEP_TYPE_SEQUENCE\x10\x01\x12\x18\n\x14TEST_STEP_TYPE_GROUP\x10\x02\x12\x19\n\x15TEST_STEP_TYPE_ACTION\x10\x03\x12\x1f\n\x1bTEST_STEP_TYPE_FLOW_CONTROL\x10\x04*\xc4\x01\n\x13TestMeasurementType\x12%\n!TEST_MEASUREMENT_TYPE_UNSPECIFIED\x10\x00\x12 \n\x1cTEST_MEASUREMENT_TYPE_DOUBLE\x10\x01\x12 \n\x1cTEST_MEASUREMENT_TYPE_STRING\x10\x03\x12!\n\x1dTEST_MEASUREMENT_TYPE_BOOLEAN\x10\x04\x12\x1f\n\x1bTEST_MEASUREMENT_TYPE_LIMIT\x10\x05\x32\xd7\x1c\n\x11TestReportService\x12\xe4\x01\n\x10ImportTestReport\x12-.sift.test_reports.v1.ImportTestReportRequest\x1a..sift.test_reports.v1.ImportTestReportResponse\"q\x92\x41H\x12\x10ImportTestReport\x1a\x34Imports a test report from an already-uploaded file.\x82\xd3\xe4\x93\x02 \"\x1b/api/v1/test-reports:import:\x01*\x12\xbe\x01\n\x10\x43reateTestReport\x12-.sift.test_reports.v1.CreateTestReportRequest\x1a..sift.test_reports.v1.CreateTestReportResponse\"K\x92\x41)\x12\x10\x43reateTestReport\x1a\x15\x43reates a test report\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/test-reports:\x01*\x12\xc4\x01\n\rGetTestReport\x12*.sift.test_reports.v1.GetTestReportRequest\x1a+.sift.test_reports.v1.GetTestReportResponse\"Z\x92\x41*\x12\rGetTestReport\x1a\x19Gets a single test report\x82\xd3\xe4\x93\x02\'\x12%/api/v1/test-reports/{test_report_id}\x12\xcc\x01\n\x0fListTestReports\x12,.sift.test_reports.v1.ListTestReportsRequest\x1a-.sift.test_reports.v1.ListTestReportsResponse\"\\\x92\x41=\x12\x0fListTestReports\x1a*Lists test reports with optional filtering\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/test-reports\x12\xbe\x01\n\x10UpdateTestReport\x12-.sift.test_reports.v1.UpdateTestReportRequest\x1a..sift.test_reports.v1.UpdateTestReportResponse\"K\x92\x41)\x12\x10UpdateTestReport\x1a\x15Updates a test report\x82\xd3\xe4\x93\x02\x19\x32\x14/api/v1/test-reports:\x01*\x12\xcc\x01\n\x10\x44\x65leteTestReport\x12-.sift.test_reports.v1.DeleteTestReportRequest\x1a..sift.test_reports.v1.DeleteTestReportResponse\"Y\x92\x41)\x12\x10\x44\x65leteTestReport\x1a\x15\x44\x65letes a test report\x82\xd3\xe4\x93\x02\'*%/api/v1/test-reports/{test_report_id}\x12\xb2\x01\n\x0e\x43reateTestStep\x12+.sift.test_reports.v1.CreateTestStepRequest\x1a,.sift.test_reports.v1.CreateTestStepResponse\"E\x92\x41%\x12\x0e\x43reateTestStep\x1a\x13\x43reates a test step\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/test-steps:\x01*\x12\xc0\x01\n\rListTestSteps\x12*.sift.test_reports.v1.ListTestStepsRequest\x1a+.sift.test_reports.v1.ListTestStepsResponse\"V\x92\x41\x39\x12\rListTestSteps\x1a(Lists test steps with optional filtering\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/test-steps\x12\xb2\x01\n\x0eUpdateTestStep\x12+.sift.test_reports.v1.UpdateTestStepRequest\x1a,.sift.test_reports.v1.UpdateTestStepResponse\"E\x92\x41%\x12\x0eUpdateTestStep\x1a\x13Updates a test step\x82\xd3\xe4\x93\x02\x17\x32\x12/api/v1/test-steps:\x01*\x12\xbe\x01\n\x0e\x44\x65leteTestStep\x12+.sift.test_reports.v1.DeleteTestStepRequest\x1a,.sift.test_reports.v1.DeleteTestStepResponse\"Q\x92\x41%\x12\x0e\x44\x65leteTestStep\x1a\x13\x44\x65letes a test step\x82\xd3\xe4\x93\x02#*!/api/v1/test-steps/{test_step_id}\x12\xdc\x01\n\x15\x43reateTestMeasurement\x12\x32.sift.test_reports.v1.CreateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.CreateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15\x43reateTestMeasurement\x1a\x1a\x43reates a test measurement\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/test-measurements:\x01*\x12\x82\x02\n\x16\x43reateTestMeasurements\x12\x33.sift.test_reports.v1.CreateTestMeasurementsRequest\x1a\x34.sift.test_reports.v1.CreateTestMeasurementsResponse\"}\x92\x41P\x12\x16\x43reateTestMeasurements\x1a\x36\x43reates multiple test measurements in a single request\x82\xd3\xe4\x93\x02$\"\x1f/api/v1/test-measurements:batch:\x01*\x12\xea\x01\n\x14ListTestMeasurements\x12\x31.sift.test_reports.v1.ListTestMeasurementsRequest\x1a\x32.sift.test_reports.v1.ListTestMeasurementsResponse\"k\x92\x41G\x12\x14ListTestMeasurements\x1a/Lists test measurements with optional filtering\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/test-measurements\x12\xcb\x01\n\x0e\x43ountTestSteps\x12+.sift.test_reports.v1.CountTestStepsRequest\x1a,.sift.test_reports.v1.CountTestStepsResponse\"^\x92\x41;\x12\x0e\x43ountTestSteps\x1a)Counts test steps with optional filtering\x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/test-steps/count\x12\xf5\x01\n\x15\x43ountTestMeasurements\x12\x32.sift.test_reports.v1.CountTestMeasurementsRequest\x1a\x33.sift.test_reports.v1.CountTestMeasurementsResponse\"s\x92\x41I\x12\x15\x43ountTestMeasurements\x1a\x30\x43ounts test measurements with optional filtering\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/test-measurements/count\x12\xdc\x01\n\x15UpdateTestMeasurement\x12\x32.sift.test_reports.v1.UpdateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.UpdateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15UpdateTestMeasurement\x1a\x1aUpdates a test measurement\x82\xd3\xe4\x93\x02\x1e\x32\x19/api/v1/test-measurements:\x01*\x12\xea\x01\n\x15\x44\x65leteTestMeasurement\x12\x32.sift.test_reports.v1.DeleteTestMeasurementRequest\x1a\x33.sift.test_reports.v1.DeleteTestMeasurementResponse\"h\x92\x41\x33\x12\x15\x44\x65leteTestMeasurement\x1a\x1a\x44\x65letes a test measurement\x82\xd3\xe4\x93\x02,**/api/v1/test-measurements/{measurement_id}\x1a#\x92\x41 \x12\x1eService to manage test reportsB\xb5\x01\n\x18\x63om.sift.test_reports.v1B\x10TestReportsProtoP\x01\xa2\x02\x03STX\xaa\x02\x13Sift.TestReports.V1\xca\x02\x13Sift\\TestReports\\V1\xe2\x02\x1fSift\\TestReports\\V1\\GPBMetadata\xea\x02\x15Sift::TestReports::V1\x92\x41\x18\x12\x16\n\x14Test Results Serviceb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'sift/test_reports/v1/test_reports.proto\x12\x14sift.test_reports.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x1fsift/metadata/v1/metadata.proto\x1a\x17sift/unit/v2/unit.proto\"\xa4\x05\n\nTestReport\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12=\n\x06status\x18\x02 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x08 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\t \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\n \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x44\n\rarchived_date\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x01R\x0c\x61rchivedDate\x12$\n\x0bis_archived\x18\r \x01(\x08\x42\x03\xe0\x41\x01R\nisArchived\x12\x1a\n\x06run_id\x18\x0e \x01(\tB\x03\xe0\x41\x01R\x05runId\"\xaf\x04\n\x08TestStep\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12)\n\x0eparent_step_id\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x0cparentStepId\x12\x17\n\x04name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x03\xe0\x41\x01R\x0b\x64\x65scription\x12\x44\n\tstep_type\x18\x06 \x01(\x0e\x32\".sift.test_reports.v1.TestStepTypeB\x03\xe0\x41\x02R\x08stepType\x12 \n\tstep_path\x18\x07 \x01(\tB\x03\xe0\x41\x02R\x08stepPath\x12=\n\x06status\x18\x08 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12>\n\nstart_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12\x43\n\nerror_info\x18\x0b \x01(\x0b\x32\x1f.sift.test_reports.v1.ErrorInfoB\x03\xe0\x41\x01R\terrorInfo\"Y\n\tErrorInfo\x12\"\n\nerror_code\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\terrorCode\x12(\n\rerror_message\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0c\x65rrorMessage\"\xab\x05\n\x0fTestMeasurement\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\x12Y\n\x10measurement_type\x18\x02 \x01(\x0e\x32).sift.test_reports.v1.TestMeasurementTypeB\x03\xe0\x41\x02R\x0fmeasurementType\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ctest_step_id\x18\x04 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12%\n\rnumeric_value\x18\x06 \x01(\x01H\x00R\x0cnumericValue\x12#\n\x0cstring_value\x18\x07 \x01(\tH\x00R\x0bstringValue\x12%\n\rboolean_value\x18\x08 \x01(\x08H\x00R\x0c\x62ooleanValue\x12+\n\x04unit\x18\t \x01(\x0b\x32\x12.sift.unit.v2.UnitB\x03\xe0\x41\x01R\x04unit\x12L\n\x0enumeric_bounds\x18\n \x01(\x0b\x32#.sift.test_reports.v1.NumericBoundsH\x01R\rnumericBounds\x12I\n\rstring_bounds\x18\x0b \x01(\x0b\x32\".sift.test_reports.v1.StringBoundsH\x01R\x0cstringBounds\x12\x1b\n\x06passed\x18\x0c \x01(\x08\x42\x03\xe0\x41\x02R\x06passed\x12=\n\ttimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\ttimestampB\x07\n\x05valueB\x08\n\x06\x62ounds\"W\n\rNumericBounds\x12\x1a\n\x03min\x18\x01 \x01(\x01\x42\x03\xe0\x41\x01H\x00R\x03min\x88\x01\x01\x12\x1a\n\x03max\x18\x02 \x01(\x01\x42\x03\xe0\x41\x01H\x01R\x03max\x88\x01\x01\x42\x06\n\x04_minB\x06\n\x04_max\":\n\x0cStringBounds\x12*\n\x0e\x65xpected_value\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rexpectedValue\"D\n\x17ImportTestReportRequest\x12)\n\x0eremote_file_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0cremoteFileId\"]\n\x18ImportTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\xfe\x03\n\x17\x43reateTestReportRequest\x12=\n\x06status\x18\x01 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x07 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\x08 \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\t \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\n \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\"]\n\x18\x43reateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"A\n\x14GetTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"Z\n\x15GetTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\x9b\x01\n\x16ListTestReportsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x86\x01\n\x17ListTestReportsResponse\x12\x43\n\x0ctest_reports\x18\x01 \x03(\x0b\x32 .sift.test_reports.v1.TestReportR\x0btestReports\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa3\x01\n\x17UpdateTestReportRequest\x12\x46\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportB\x03\xe0\x41\x02R\ntestReport\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"]\n\x18UpdateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"D\n\x17\x44\x65leteTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"\x1a\n\x18\x44\x65leteTestReportResponse\"Y\n\x15\x43reateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\"U\n\x16\x43reateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\"\x99\x01\n\x14ListTestStepsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"~\n\x15ListTestStepsResponse\x12=\n\ntest_steps\x18\x01 \x03(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\ttestSteps\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x9b\x01\n\x15UpdateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"U\n\x16UpdateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\">\n\x15\x44\x65leteTestStepRequest\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\"\x18\n\x16\x44\x65leteTestStepResponse\"u\n\x1c\x43reateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\"q\n\x1d\x43reateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"x\n\x1d\x43reateTestMeasurementsRequest\x12W\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x10testMeasurements\"\x91\x01\n\x1e\x43reateTestMeasurementsResponse\x12\x41\n\x1ameasurements_created_count\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\x18measurementsCreatedCount\x12,\n\x0fmeasurement_ids\x18\x02 \x03(\tB\x03\xe0\x41\x02R\x0emeasurementIds\"\xa0\x01\n\x1bListTestMeasurementsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x9a\x01\n\x1cListTestMeasurementsResponse\x12R\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x10testMeasurements\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"4\n\x15\x43ountTestStepsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\".\n\x16\x43ountTestStepsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\";\n\x1c\x43ountTestMeasurementsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"5\n\x1d\x43ountTestMeasurementsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\"\xb7\x01\n\x1cUpdateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"q\n\x1dUpdateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"J\n\x1c\x44\x65leteTestMeasurementRequest\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\"\x1f\n\x1d\x44\x65leteTestMeasurementResponse*\xd6\x01\n\nTestStatus\x12\x1b\n\x17TEST_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11TEST_STATUS_DRAFT\x10\x01\x12\x16\n\x12TEST_STATUS_PASSED\x10\x02\x12\x16\n\x12TEST_STATUS_FAILED\x10\x03\x12\x17\n\x13TEST_STATUS_ABORTED\x10\x04\x12\x15\n\x11TEST_STATUS_ERROR\x10\x05\x12\x1b\n\x17TEST_STATUS_IN_PROGRESS\x10\x06\x12\x17\n\x13TEST_STATUS_SKIPPED\x10\x07*\xa1\x01\n\x0cTestStepType\x12\x1e\n\x1aTEST_STEP_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TEST_STEP_TYPE_SEQUENCE\x10\x01\x12\x18\n\x14TEST_STEP_TYPE_GROUP\x10\x02\x12\x19\n\x15TEST_STEP_TYPE_ACTION\x10\x03\x12\x1f\n\x1bTEST_STEP_TYPE_FLOW_CONTROL\x10\x04*\xc4\x01\n\x13TestMeasurementType\x12%\n!TEST_MEASUREMENT_TYPE_UNSPECIFIED\x10\x00\x12 \n\x1cTEST_MEASUREMENT_TYPE_DOUBLE\x10\x01\x12 \n\x1cTEST_MEASUREMENT_TYPE_STRING\x10\x03\x12!\n\x1dTEST_MEASUREMENT_TYPE_BOOLEAN\x10\x04\x12\x1f\n\x1bTEST_MEASUREMENT_TYPE_LIMIT\x10\x05\x32\xd7\x1c\n\x11TestReportService\x12\xe4\x01\n\x10ImportTestReport\x12-.sift.test_reports.v1.ImportTestReportRequest\x1a..sift.test_reports.v1.ImportTestReportResponse\"q\x92\x41H\x12\x10ImportTestReport\x1a\x34Imports a test report from an already-uploaded file.\x82\xd3\xe4\x93\x02 \"\x1b/api/v1/test-reports:import:\x01*\x12\xbe\x01\n\x10\x43reateTestReport\x12-.sift.test_reports.v1.CreateTestReportRequest\x1a..sift.test_reports.v1.CreateTestReportResponse\"K\x92\x41)\x12\x10\x43reateTestReport\x1a\x15\x43reates a test report\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/test-reports:\x01*\x12\xc4\x01\n\rGetTestReport\x12*.sift.test_reports.v1.GetTestReportRequest\x1a+.sift.test_reports.v1.GetTestReportResponse\"Z\x92\x41*\x12\rGetTestReport\x1a\x19Gets a single test report\x82\xd3\xe4\x93\x02\'\x12%/api/v1/test-reports/{test_report_id}\x12\xcc\x01\n\x0fListTestReports\x12,.sift.test_reports.v1.ListTestReportsRequest\x1a-.sift.test_reports.v1.ListTestReportsResponse\"\\\x92\x41=\x12\x0fListTestReports\x1a*Lists test reports with optional filtering\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/test-reports\x12\xbe\x01\n\x10UpdateTestReport\x12-.sift.test_reports.v1.UpdateTestReportRequest\x1a..sift.test_reports.v1.UpdateTestReportResponse\"K\x92\x41)\x12\x10UpdateTestReport\x1a\x15Updates a test report\x82\xd3\xe4\x93\x02\x19\x32\x14/api/v1/test-reports:\x01*\x12\xcc\x01\n\x10\x44\x65leteTestReport\x12-.sift.test_reports.v1.DeleteTestReportRequest\x1a..sift.test_reports.v1.DeleteTestReportResponse\"Y\x92\x41)\x12\x10\x44\x65leteTestReport\x1a\x15\x44\x65letes a test report\x82\xd3\xe4\x93\x02\'*%/api/v1/test-reports/{test_report_id}\x12\xb2\x01\n\x0e\x43reateTestStep\x12+.sift.test_reports.v1.CreateTestStepRequest\x1a,.sift.test_reports.v1.CreateTestStepResponse\"E\x92\x41%\x12\x0e\x43reateTestStep\x1a\x13\x43reates a test step\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/test-steps:\x01*\x12\xc0\x01\n\rListTestSteps\x12*.sift.test_reports.v1.ListTestStepsRequest\x1a+.sift.test_reports.v1.ListTestStepsResponse\"V\x92\x41\x39\x12\rListTestSteps\x1a(Lists test steps with optional filtering\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/test-steps\x12\xb2\x01\n\x0eUpdateTestStep\x12+.sift.test_reports.v1.UpdateTestStepRequest\x1a,.sift.test_reports.v1.UpdateTestStepResponse\"E\x92\x41%\x12\x0eUpdateTestStep\x1a\x13Updates a test step\x82\xd3\xe4\x93\x02\x17\x32\x12/api/v1/test-steps:\x01*\x12\xbe\x01\n\x0e\x44\x65leteTestStep\x12+.sift.test_reports.v1.DeleteTestStepRequest\x1a,.sift.test_reports.v1.DeleteTestStepResponse\"Q\x92\x41%\x12\x0e\x44\x65leteTestStep\x1a\x13\x44\x65letes a test step\x82\xd3\xe4\x93\x02#*!/api/v1/test-steps/{test_step_id}\x12\xdc\x01\n\x15\x43reateTestMeasurement\x12\x32.sift.test_reports.v1.CreateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.CreateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15\x43reateTestMeasurement\x1a\x1a\x43reates a test measurement\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/test-measurements:\x01*\x12\x82\x02\n\x16\x43reateTestMeasurements\x12\x33.sift.test_reports.v1.CreateTestMeasurementsRequest\x1a\x34.sift.test_reports.v1.CreateTestMeasurementsResponse\"}\x92\x41P\x12\x16\x43reateTestMeasurements\x1a\x36\x43reates multiple test measurements in a single request\x82\xd3\xe4\x93\x02$\"\x1f/api/v1/test-measurements:batch:\x01*\x12\xea\x01\n\x14ListTestMeasurements\x12\x31.sift.test_reports.v1.ListTestMeasurementsRequest\x1a\x32.sift.test_reports.v1.ListTestMeasurementsResponse\"k\x92\x41G\x12\x14ListTestMeasurements\x1a/Lists test measurements with optional filtering\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/test-measurements\x12\xcb\x01\n\x0e\x43ountTestSteps\x12+.sift.test_reports.v1.CountTestStepsRequest\x1a,.sift.test_reports.v1.CountTestStepsResponse\"^\x92\x41;\x12\x0e\x43ountTestSteps\x1a)Counts test steps with optional filtering\x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/test-steps/count\x12\xf5\x01\n\x15\x43ountTestMeasurements\x12\x32.sift.test_reports.v1.CountTestMeasurementsRequest\x1a\x33.sift.test_reports.v1.CountTestMeasurementsResponse\"s\x92\x41I\x12\x15\x43ountTestMeasurements\x1a\x30\x43ounts test measurements with optional filtering\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/test-measurements/count\x12\xdc\x01\n\x15UpdateTestMeasurement\x12\x32.sift.test_reports.v1.UpdateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.UpdateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15UpdateTestMeasurement\x1a\x1aUpdates a test measurement\x82\xd3\xe4\x93\x02\x1e\x32\x19/api/v1/test-measurements:\x01*\x12\xea\x01\n\x15\x44\x65leteTestMeasurement\x12\x32.sift.test_reports.v1.DeleteTestMeasurementRequest\x1a\x33.sift.test_reports.v1.DeleteTestMeasurementResponse\"h\x92\x41\x33\x12\x15\x44\x65leteTestMeasurement\x1a\x1a\x44\x65letes a test measurement\x82\xd3\xe4\x93\x02,**/api/v1/test-measurements/{measurement_id}\x1a#\x92\x41 \x12\x1eService to manage test reportsB\xb5\x01\n\x18\x63om.sift.test_reports.v1B\x10TestReportsProtoP\x01\xa2\x02\x03STX\xaa\x02\x13Sift.TestReports.V1\xca\x02\x13Sift\\TestReports\\V1\xe2\x02\x1fSift\\TestReports\\V1\\GPBMetadata\xea\x02\x15Sift::TestReports::V1\x92\x41\x18\x12\x16\n\x14Test Results Serviceb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -55,6 +55,8 @@ _globals['_TESTREPORT'].fields_by_name['archived_date']._serialized_options = b'\340A\001' _globals['_TESTREPORT'].fields_by_name['is_archived']._loaded_options = None _globals['_TESTREPORT'].fields_by_name['is_archived']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['run_id']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['run_id']._serialized_options = b'\340A\001' _globals['_TESTSTEP'].fields_by_name['test_step_id']._loaded_options = None _globals['_TESTSTEP'].fields_by_name['test_step_id']._serialized_options = b'\340A\002' _globals['_TESTSTEP'].fields_by_name['test_report_id']._loaded_options = None @@ -219,92 +221,92 @@ _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestMeasurement']._serialized_options = b'\222A3\022\025UpdateTestMeasurement\032\032Updates a test measurement\202\323\344\223\002\0362\031/api/v1/test-measurements:\001*' _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._loaded_options = None _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._serialized_options = b'\222A3\022\025DeleteTestMeasurement\032\032Deletes a test measurement\202\323\344\223\002,**/api/v1/test-measurements/{measurement_id}' - _globals['_TESTSTATUS']._serialized_start=6276 - _globals['_TESTSTATUS']._serialized_end=6490 - _globals['_TESTSTEPTYPE']._serialized_start=6493 - _globals['_TESTSTEPTYPE']._serialized_end=6654 - _globals['_TESTMEASUREMENTTYPE']._serialized_start=6657 - _globals['_TESTMEASUREMENTTYPE']._serialized_end=6853 + _globals['_TESTSTATUS']._serialized_start=6304 + _globals['_TESTSTATUS']._serialized_end=6518 + _globals['_TESTSTEPTYPE']._serialized_start=6521 + _globals['_TESTSTEPTYPE']._serialized_end=6682 + _globals['_TESTMEASUREMENTTYPE']._serialized_start=6685 + _globals['_TESTMEASUREMENTTYPE']._serialized_end=6881 _globals['_TESTREPORT']._serialized_start=302 - _globals['_TESTREPORT']._serialized_end=950 - _globals['_TESTSTEP']._serialized_start=953 - _globals['_TESTSTEP']._serialized_end=1512 - _globals['_ERRORINFO']._serialized_start=1514 - _globals['_ERRORINFO']._serialized_end=1603 - _globals['_TESTMEASUREMENT']._serialized_start=1606 - _globals['_TESTMEASUREMENT']._serialized_end=2289 - _globals['_NUMERICBOUNDS']._serialized_start=2291 - _globals['_NUMERICBOUNDS']._serialized_end=2378 - _globals['_STRINGBOUNDS']._serialized_start=2380 - _globals['_STRINGBOUNDS']._serialized_end=2438 - _globals['_IMPORTTESTREPORTREQUEST']._serialized_start=2440 - _globals['_IMPORTTESTREPORTREQUEST']._serialized_end=2508 - _globals['_IMPORTTESTREPORTRESPONSE']._serialized_start=2510 - _globals['_IMPORTTESTREPORTRESPONSE']._serialized_end=2603 - _globals['_CREATETESTREPORTREQUEST']._serialized_start=2606 - _globals['_CREATETESTREPORTREQUEST']._serialized_end=3116 - _globals['_CREATETESTREPORTRESPONSE']._serialized_start=3118 - _globals['_CREATETESTREPORTRESPONSE']._serialized_end=3211 - _globals['_GETTESTREPORTREQUEST']._serialized_start=3213 - _globals['_GETTESTREPORTREQUEST']._serialized_end=3278 - _globals['_GETTESTREPORTRESPONSE']._serialized_start=3280 - _globals['_GETTESTREPORTRESPONSE']._serialized_end=3370 - _globals['_LISTTESTREPORTSREQUEST']._serialized_start=3373 - _globals['_LISTTESTREPORTSREQUEST']._serialized_end=3528 - _globals['_LISTTESTREPORTSRESPONSE']._serialized_start=3531 - _globals['_LISTTESTREPORTSRESPONSE']._serialized_end=3665 - _globals['_UPDATETESTREPORTREQUEST']._serialized_start=3668 - _globals['_UPDATETESTREPORTREQUEST']._serialized_end=3831 - _globals['_UPDATETESTREPORTRESPONSE']._serialized_start=3833 - _globals['_UPDATETESTREPORTRESPONSE']._serialized_end=3926 - _globals['_DELETETESTREPORTREQUEST']._serialized_start=3928 - _globals['_DELETETESTREPORTREQUEST']._serialized_end=3996 - _globals['_DELETETESTREPORTRESPONSE']._serialized_start=3998 - _globals['_DELETETESTREPORTRESPONSE']._serialized_end=4024 - _globals['_CREATETESTSTEPREQUEST']._serialized_start=4026 - _globals['_CREATETESTSTEPREQUEST']._serialized_end=4115 - _globals['_CREATETESTSTEPRESPONSE']._serialized_start=4117 - _globals['_CREATETESTSTEPRESPONSE']._serialized_end=4202 - _globals['_LISTTESTSTEPSREQUEST']._serialized_start=4205 - _globals['_LISTTESTSTEPSREQUEST']._serialized_end=4358 - _globals['_LISTTESTSTEPSRESPONSE']._serialized_start=4360 - _globals['_LISTTESTSTEPSRESPONSE']._serialized_end=4486 - _globals['_UPDATETESTSTEPREQUEST']._serialized_start=4489 - _globals['_UPDATETESTSTEPREQUEST']._serialized_end=4644 - _globals['_UPDATETESTSTEPRESPONSE']._serialized_start=4646 - _globals['_UPDATETESTSTEPRESPONSE']._serialized_end=4731 - _globals['_DELETETESTSTEPREQUEST']._serialized_start=4733 - _globals['_DELETETESTSTEPREQUEST']._serialized_end=4795 - _globals['_DELETETESTSTEPRESPONSE']._serialized_start=4797 - _globals['_DELETETESTSTEPRESPONSE']._serialized_end=4821 - _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_start=4823 - _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_end=4940 - _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_start=4942 - _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_end=5055 - _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_start=5057 - _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_end=5177 - _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_start=5180 - _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_end=5325 - _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_start=5328 - _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_end=5488 - _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_start=5491 - _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_end=5645 - _globals['_COUNTTESTSTEPSREQUEST']._serialized_start=5647 - _globals['_COUNTTESTSTEPSREQUEST']._serialized_end=5699 - _globals['_COUNTTESTSTEPSRESPONSE']._serialized_start=5701 - _globals['_COUNTTESTSTEPSRESPONSE']._serialized_end=5747 - _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_start=5749 - _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_end=5808 - _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_start=5810 - _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_end=5863 - _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_start=5866 - _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_end=6049 - _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_start=6051 - _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_end=6164 - _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_start=6166 - _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_end=6240 - _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_start=6242 - _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_end=6273 - _globals['_TESTREPORTSERVICE']._serialized_start=6856 - _globals['_TESTREPORTSERVICE']._serialized_end=10527 + _globals['_TESTREPORT']._serialized_end=978 + _globals['_TESTSTEP']._serialized_start=981 + _globals['_TESTSTEP']._serialized_end=1540 + _globals['_ERRORINFO']._serialized_start=1542 + _globals['_ERRORINFO']._serialized_end=1631 + _globals['_TESTMEASUREMENT']._serialized_start=1634 + _globals['_TESTMEASUREMENT']._serialized_end=2317 + _globals['_NUMERICBOUNDS']._serialized_start=2319 + _globals['_NUMERICBOUNDS']._serialized_end=2406 + _globals['_STRINGBOUNDS']._serialized_start=2408 + _globals['_STRINGBOUNDS']._serialized_end=2466 + _globals['_IMPORTTESTREPORTREQUEST']._serialized_start=2468 + _globals['_IMPORTTESTREPORTREQUEST']._serialized_end=2536 + _globals['_IMPORTTESTREPORTRESPONSE']._serialized_start=2538 + _globals['_IMPORTTESTREPORTRESPONSE']._serialized_end=2631 + _globals['_CREATETESTREPORTREQUEST']._serialized_start=2634 + _globals['_CREATETESTREPORTREQUEST']._serialized_end=3144 + _globals['_CREATETESTREPORTRESPONSE']._serialized_start=3146 + _globals['_CREATETESTREPORTRESPONSE']._serialized_end=3239 + _globals['_GETTESTREPORTREQUEST']._serialized_start=3241 + _globals['_GETTESTREPORTREQUEST']._serialized_end=3306 + _globals['_GETTESTREPORTRESPONSE']._serialized_start=3308 + _globals['_GETTESTREPORTRESPONSE']._serialized_end=3398 + _globals['_LISTTESTREPORTSREQUEST']._serialized_start=3401 + _globals['_LISTTESTREPORTSREQUEST']._serialized_end=3556 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_start=3559 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_end=3693 + _globals['_UPDATETESTREPORTREQUEST']._serialized_start=3696 + _globals['_UPDATETESTREPORTREQUEST']._serialized_end=3859 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_start=3861 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_end=3954 + _globals['_DELETETESTREPORTREQUEST']._serialized_start=3956 + _globals['_DELETETESTREPORTREQUEST']._serialized_end=4024 + _globals['_DELETETESTREPORTRESPONSE']._serialized_start=4026 + _globals['_DELETETESTREPORTRESPONSE']._serialized_end=4052 + _globals['_CREATETESTSTEPREQUEST']._serialized_start=4054 + _globals['_CREATETESTSTEPREQUEST']._serialized_end=4143 + _globals['_CREATETESTSTEPRESPONSE']._serialized_start=4145 + _globals['_CREATETESTSTEPRESPONSE']._serialized_end=4230 + _globals['_LISTTESTSTEPSREQUEST']._serialized_start=4233 + _globals['_LISTTESTSTEPSREQUEST']._serialized_end=4386 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_start=4388 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_end=4514 + _globals['_UPDATETESTSTEPREQUEST']._serialized_start=4517 + _globals['_UPDATETESTSTEPREQUEST']._serialized_end=4672 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_start=4674 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_end=4759 + _globals['_DELETETESTSTEPREQUEST']._serialized_start=4761 + _globals['_DELETETESTSTEPREQUEST']._serialized_end=4823 + _globals['_DELETETESTSTEPRESPONSE']._serialized_start=4825 + _globals['_DELETETESTSTEPRESPONSE']._serialized_end=4849 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_start=4851 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_end=4968 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_start=4970 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_end=5083 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_start=5085 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_end=5205 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_start=5208 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_end=5353 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_start=5356 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_end=5516 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_start=5519 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_end=5673 + _globals['_COUNTTESTSTEPSREQUEST']._serialized_start=5675 + _globals['_COUNTTESTSTEPSREQUEST']._serialized_end=5727 + _globals['_COUNTTESTSTEPSRESPONSE']._serialized_start=5729 + _globals['_COUNTTESTSTEPSRESPONSE']._serialized_end=5775 + _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_start=5777 + _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_end=5836 + _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_start=5838 + _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_end=5891 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_start=5894 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_end=6077 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_start=6079 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_end=6192 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_start=6194 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_end=6268 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_start=6270 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_end=6301 + _globals['_TESTREPORTSERVICE']._serialized_start=6884 + _globals['_TESTREPORTSERVICE']._serialized_end=10555 # @@protoc_insertion_point(module_scope) diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.pyi b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi index 968729bb6..354d40623 100644 --- a/python/lib/sift/test_reports/v1/test_reports_pb2.pyi +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi @@ -119,6 +119,7 @@ class TestReport(google.protobuf.message.Message): SYSTEM_OPERATOR_FIELD_NUMBER: builtins.int ARCHIVED_DATE_FIELD_NUMBER: builtins.int IS_ARCHIVED_FIELD_NUMBER: builtins.int + RUN_ID_FIELD_NUMBER: builtins.int test_report_id: builtins.str """Unique identifier for the run""" status: global___TestStatus.ValueType @@ -137,6 +138,8 @@ class TestReport(google.protobuf.message.Message): """Unique identifier for user owner""" is_archived: builtins.bool """Whether the test run is archived (externally exposed)""" + run_id: builtins.str + """The run ID for the test run""" @property def start_time(self) -> google.protobuf.timestamp_pb2.Timestamp: """The start time of the test run""" @@ -169,9 +172,10 @@ class TestReport(google.protobuf.message.Message): system_operator: builtins.str = ..., archived_date: google.protobuf.timestamp_pb2.Timestamp | None = ..., is_archived: builtins.bool = ..., + run_id: builtins.str = ..., ) -> None: ... def HasField(self, field_name: typing.Literal["archived_date", b"archived_date", "end_time", b"end_time", "start_time", b"start_time"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["archived_date", b"archived_date", "end_time", b"end_time", "is_archived", b"is_archived", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_report_id", b"test_report_id", "test_system_name", b"test_system_name"]) -> None: ... + def ClearField(self, field_name: typing.Literal["archived_date", b"archived_date", "end_time", b"end_time", "is_archived", b"is_archived", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "run_id", b"run_id", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_report_id", b"test_report_id", "test_system_name", b"test_system_name"]) -> None: ... global___TestReport = TestReport @@ -540,7 +544,7 @@ class ListTestReportsRequest(google.protobuf.message.Message): """A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. Available fields to filter by are `test_report_id`, `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, `created_by_user_id`, `modified_by_user_id`, - `part_number`, `system_operator`, `archived_date`, and `metadata`. + `part_number`, `system_operator`, `run_id`, `archived_date`, and `metadata`. Metadata can be used in filters by using `metadata.{metadata_key_name}` as the field name. For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testreport). Optional. @@ -605,7 +609,7 @@ class UpdateTestReportRequest(google.protobuf.message.Message): def update_mask(self) -> google.protobuf.field_mask_pb2.FieldMask: """The field mask specifying which fields to update. The fields available to be updated are `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, - `part_number`, `system_operator`, and `is_archived`. + `part_number`, `system_operator`, `run_id`, and `is_archived`. """ def __init__( diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index e9a8bd460..78c0f83b2 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -72,4 +72,4 @@ def ci_pytest_tag(sift_client): return tag -from sift_client.util.test_results.pytest import report_context, step, module_substep # noqa: F401 +from sift_client.util.test_results.pytest import module_substep, report_context, step # noqa: F401 diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py index d180c8de3..c8006ef8f 100644 --- a/python/lib/sift_client/sift_types/test_report.py +++ b/python/lib/sift_client/sift_types/test_report.py @@ -69,101 +69,6 @@ class TestMeasurementType(Enum): LIMIT = 5 -class TestReportBase(ModelCreateUpdateBase): - """Base model for TestReportUpdate and TestReportCreate. Contains shared fields for all test reports. Update and create models differ mostly in what fields are required vs optional.""" - - status: TestStatus | None = None - metadata: dict[str, str | float | bool] | None = None - serial_number: str | None = None - part_number: str | None = None - system_operator: str | None = None - - _to_proto_helpers: ClassVar[dict[str, MappingHelper]] = { - "metadata": MappingHelper( - proto_attr_path="metadata", update_field="metadata", converter=metadata_dict_to_proto - ), - } - - def _get_proto_class(self) -> type[TestReportProto]: - return TestReportProto - - -class TestReportUpdate(TestReportBase, ModelUpdate[TestReportProto]): - """Update model for TestReport.""" - - name: str | None = None - test_system_name: str | None = None - test_case: str | None = None - start_time: datetime | None = None - end_time: datetime | None = None - - is_archived: bool | None = None - - def _add_resource_id_to_proto(self, proto_msg: TestReportProto): - if self._resource_id is None: - raise ValueError("Resource ID must be set before adding to proto") - proto_msg.test_report_id = self._resource_id - - -class TestReportCreate(TestReportBase, ModelCreate[TestReportProto]): - """Create model for TestReport.""" - - name: str - test_system_name: str - test_case: str - start_time: datetime - end_time: datetime - - def to_proto(self) -> TestReportProto: - """Convert to protobuf message with custom logic.""" - proto = TestReportProto( - status=self.status.value, # type: ignore - name=self.name, - test_system_name=self.test_system_name, - test_case=self.test_case, - metadata=metadata_dict_to_proto(self.metadata) if self.metadata else {}, - is_archived=False, - ) - - proto.start_time.FromDatetime(self.start_time) - proto.end_time.FromDatetime(self.end_time) - - if self.serial_number: - proto.serial_number = self.serial_number - - if self.part_number: - proto.part_number = self.part_number - - if self.system_operator: - proto.system_operator = self.system_operator - - return proto - - -class ErrorInfo(BaseType[ErrorInfoProto, "ErrorInfo"]): - """ErrorInfo model representing error information in a test step.""" - - error_code: int - error_message: str - - @classmethod - def _from_proto(cls, proto: ErrorInfoProto, sift_client: SiftClient | None = None) -> ErrorInfo: - return cls( - proto=proto, - id_=None, - error_code=proto.error_code, - error_message=proto.error_message, - _client=sift_client, - ) - - def _to_proto(self) -> ErrorInfoProto: - """Convert to protobuf message.""" - return ErrorInfoProto( - error_code=self.error_code, - error_message=self.error_message, - ) - - class TestStepBase(ModelCreateUpdateBase): """Base model for TestStepUpdate and TestStepCreate. Contains shared fields for all test steps. Update and create models differ mostly in what fields are required vs optional.""" @@ -512,6 +417,102 @@ def update( return self +class TestReportBase(ModelCreateUpdateBase): + """Base model for TestReportUpdate and TestReportCreate. Contains shared fields for all test reports. Update and create models differ mostly in what fields are required vs optional.""" + + status: TestStatus | None = None + metadata: dict[str, str | float | bool] | None = None + serial_number: str | None = None + part_number: str | None = None + system_operator: str | None = None + run_id: str | None = None + + _to_proto_helpers: ClassVar[dict[str, MappingHelper]] = { + "metadata": MappingHelper( + proto_attr_path="metadata", update_field="metadata", converter=metadata_dict_to_proto + ), + } + + def _get_proto_class(self) -> type[TestReportProto]: + return TestReportProto + + +class TestReportUpdate(TestReportBase, ModelUpdate[TestReportProto]): + """Update model for TestReport.""" + + name: str | None = None + test_system_name: str | None = None + test_case: str | None = None + start_time: datetime | None = None + end_time: datetime | None = None + + is_archived: bool | None = None + + def _add_resource_id_to_proto(self, proto_msg: TestReportProto): + if self._resource_id is None: + raise ValueError("Resource ID must be set before adding to proto") + proto_msg.test_report_id = self._resource_id + + +class TestReportCreate(TestReportBase, ModelCreate[TestReportProto]): + """Create model for TestReport.""" + + name: str + test_system_name: str + test_case: str + start_time: datetime + end_time: datetime + + def to_proto(self) -> TestReportProto: + """Convert to protobuf message with custom logic.""" + proto = TestReportProto( + status=self.status.value, # type: ignore + name=self.name, + test_system_name=self.test_system_name, + test_case=self.test_case, + metadata=metadata_dict_to_proto(self.metadata) if self.metadata else {}, + is_archived=False, + ) + + proto.start_time.FromDatetime(self.start_time) + proto.end_time.FromDatetime(self.end_time) + + if self.serial_number: + proto.serial_number = self.serial_number + + if self.part_number: + proto.part_number = self.part_number + + if self.system_operator: + proto.system_operator = self.system_operator + + return proto + + +class ErrorInfo(BaseType[ErrorInfoProto, "ErrorInfo"]): + """ErrorInfo model representing error information in a test step.""" + + error_code: int + error_message: str + + @classmethod + def _from_proto(cls, proto: ErrorInfoProto, sift_client: SiftClient | None = None) -> ErrorInfo: + return cls( + proto=proto, + id_=None, + error_code=proto.error_code, + error_message=proto.error_message, + _client=sift_client, + ) + + def _to_proto(self) -> ErrorInfoProto: + """Convert to protobuf message.""" + return ErrorInfoProto( + error_code=self.error_code, + error_message=self.error_message, + ) + + class TestReport(BaseType[TestReportProto, "TestReport"]): """TestReport model representing a test report.""" diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index e11114fd4..ff553ce94 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -18,7 +18,6 @@ TestStepType, ) from sift_client.util.test_results.bounds import ( - assign_value_to_measurement, evaluate_measurement_bounds, ) diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 048aea4f4..60b5e54ca 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -1,17 +1,17 @@ from __future__ import annotations from datetime import datetime, timezone +from pathlib import Path from typing import TYPE_CHECKING -import sys + import pytest -from pathlib import Path + from sift_client.util.test_results import ReportContext if TYPE_CHECKING: from sift_client.client import SiftClient -# TODO FIGURE OUT HOW TO EXPORT THIS BETTER @pytest.fixture(scope="session", autouse=True) def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> ReportContext: """Create a report context for the session.""" @@ -34,9 +34,7 @@ def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> R def step(report_context: ReportContext, request: pytest.FixtureRequest): """Create an outer step for the function.""" name = str(request.node.name) - print("Step fixture: function name in step", name) with report_context.new_step(name=name) as new_step: - print("Step fixture: step", new_step) yield new_step From 4462b631a3943d21b58d35dba6dc0ced67726472 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Thu, 23 Oct 2025 19:04:24 -0700 Subject: [PATCH 05/16] Add integration test for test report utils --- .../sift/test_reports/v1/test_reports.proto | 3 + .../sift/test_reports/v1/test_reports_pb2.py | 146 +++---- .../sift/test_reports/v1/test_reports_pb2.pyi | 6 +- .../low_level_wrappers/test_results.py | 40 +- .../_tests/resources/test_test_results.py | 13 +- .../_tests/util/test_test_results_utils.py | 357 ++++++++++++++++++ .../lib/sift_client/sift_types/test_report.py | 28 +- .../sift_client/util/test_results/bounds.py | 13 +- .../util/test_results/context_manager.py | 41 +- .../sift_client/util/test_results/pytest.py | 5 +- 10 files changed, 502 insertions(+), 150 deletions(-) diff --git a/protos/sift/test_reports/v1/test_reports.proto b/protos/sift/test_reports/v1/test_reports.proto index 16285b3dc..ce36eabed 100644 --- a/protos/sift/test_reports/v1/test_reports.proto +++ b/protos/sift/test_reports/v1/test_reports.proto @@ -381,6 +381,9 @@ message CreateTestReportRequest { // Unique identifier for user owner string system_operator = 10 [(google.api.field_behavior) = OPTIONAL]; + + // The run ID for the test report + string run_id = 11 [(google.api.field_behavior) = OPTIONAL]; } // Response message for CreateTestReport diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.py b/python/lib/sift/test_reports/v1/test_reports_pb2.py index 7c7aaa2b5..41bf2a24c 100644 --- a/python/lib/sift/test_reports/v1/test_reports_pb2.py +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.py @@ -21,7 +21,7 @@ from sift.unit.v2 import unit_pb2 as sift_dot_unit_dot_v2_dot_unit__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'sift/test_reports/v1/test_reports.proto\x12\x14sift.test_reports.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x1fsift/metadata/v1/metadata.proto\x1a\x17sift/unit/v2/unit.proto\"\xa4\x05\n\nTestReport\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12=\n\x06status\x18\x02 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x08 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\t \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\n \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x44\n\rarchived_date\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x01R\x0c\x61rchivedDate\x12$\n\x0bis_archived\x18\r \x01(\x08\x42\x03\xe0\x41\x01R\nisArchived\x12\x1a\n\x06run_id\x18\x0e \x01(\tB\x03\xe0\x41\x01R\x05runId\"\xaf\x04\n\x08TestStep\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12)\n\x0eparent_step_id\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x0cparentStepId\x12\x17\n\x04name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x03\xe0\x41\x01R\x0b\x64\x65scription\x12\x44\n\tstep_type\x18\x06 \x01(\x0e\x32\".sift.test_reports.v1.TestStepTypeB\x03\xe0\x41\x02R\x08stepType\x12 \n\tstep_path\x18\x07 \x01(\tB\x03\xe0\x41\x02R\x08stepPath\x12=\n\x06status\x18\x08 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12>\n\nstart_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12\x43\n\nerror_info\x18\x0b \x01(\x0b\x32\x1f.sift.test_reports.v1.ErrorInfoB\x03\xe0\x41\x01R\terrorInfo\"Y\n\tErrorInfo\x12\"\n\nerror_code\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\terrorCode\x12(\n\rerror_message\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0c\x65rrorMessage\"\xab\x05\n\x0fTestMeasurement\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\x12Y\n\x10measurement_type\x18\x02 \x01(\x0e\x32).sift.test_reports.v1.TestMeasurementTypeB\x03\xe0\x41\x02R\x0fmeasurementType\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ctest_step_id\x18\x04 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12%\n\rnumeric_value\x18\x06 \x01(\x01H\x00R\x0cnumericValue\x12#\n\x0cstring_value\x18\x07 \x01(\tH\x00R\x0bstringValue\x12%\n\rboolean_value\x18\x08 \x01(\x08H\x00R\x0c\x62ooleanValue\x12+\n\x04unit\x18\t \x01(\x0b\x32\x12.sift.unit.v2.UnitB\x03\xe0\x41\x01R\x04unit\x12L\n\x0enumeric_bounds\x18\n \x01(\x0b\x32#.sift.test_reports.v1.NumericBoundsH\x01R\rnumericBounds\x12I\n\rstring_bounds\x18\x0b \x01(\x0b\x32\".sift.test_reports.v1.StringBoundsH\x01R\x0cstringBounds\x12\x1b\n\x06passed\x18\x0c \x01(\x08\x42\x03\xe0\x41\x02R\x06passed\x12=\n\ttimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\ttimestampB\x07\n\x05valueB\x08\n\x06\x62ounds\"W\n\rNumericBounds\x12\x1a\n\x03min\x18\x01 \x01(\x01\x42\x03\xe0\x41\x01H\x00R\x03min\x88\x01\x01\x12\x1a\n\x03max\x18\x02 \x01(\x01\x42\x03\xe0\x41\x01H\x01R\x03max\x88\x01\x01\x42\x06\n\x04_minB\x06\n\x04_max\":\n\x0cStringBounds\x12*\n\x0e\x65xpected_value\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rexpectedValue\"D\n\x17ImportTestReportRequest\x12)\n\x0eremote_file_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0cremoteFileId\"]\n\x18ImportTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\xfe\x03\n\x17\x43reateTestReportRequest\x12=\n\x06status\x18\x01 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x07 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\x08 \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\t \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\n \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\"]\n\x18\x43reateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"A\n\x14GetTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"Z\n\x15GetTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\x9b\x01\n\x16ListTestReportsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x86\x01\n\x17ListTestReportsResponse\x12\x43\n\x0ctest_reports\x18\x01 \x03(\x0b\x32 .sift.test_reports.v1.TestReportR\x0btestReports\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa3\x01\n\x17UpdateTestReportRequest\x12\x46\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportB\x03\xe0\x41\x02R\ntestReport\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"]\n\x18UpdateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"D\n\x17\x44\x65leteTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"\x1a\n\x18\x44\x65leteTestReportResponse\"Y\n\x15\x43reateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\"U\n\x16\x43reateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\"\x99\x01\n\x14ListTestStepsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"~\n\x15ListTestStepsResponse\x12=\n\ntest_steps\x18\x01 \x03(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\ttestSteps\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x9b\x01\n\x15UpdateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"U\n\x16UpdateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\">\n\x15\x44\x65leteTestStepRequest\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\"\x18\n\x16\x44\x65leteTestStepResponse\"u\n\x1c\x43reateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\"q\n\x1d\x43reateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"x\n\x1d\x43reateTestMeasurementsRequest\x12W\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x10testMeasurements\"\x91\x01\n\x1e\x43reateTestMeasurementsResponse\x12\x41\n\x1ameasurements_created_count\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\x18measurementsCreatedCount\x12,\n\x0fmeasurement_ids\x18\x02 \x03(\tB\x03\xe0\x41\x02R\x0emeasurementIds\"\xa0\x01\n\x1bListTestMeasurementsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x9a\x01\n\x1cListTestMeasurementsResponse\x12R\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x10testMeasurements\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"4\n\x15\x43ountTestStepsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\".\n\x16\x43ountTestStepsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\";\n\x1c\x43ountTestMeasurementsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"5\n\x1d\x43ountTestMeasurementsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\"\xb7\x01\n\x1cUpdateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"q\n\x1dUpdateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"J\n\x1c\x44\x65leteTestMeasurementRequest\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\"\x1f\n\x1d\x44\x65leteTestMeasurementResponse*\xd6\x01\n\nTestStatus\x12\x1b\n\x17TEST_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11TEST_STATUS_DRAFT\x10\x01\x12\x16\n\x12TEST_STATUS_PASSED\x10\x02\x12\x16\n\x12TEST_STATUS_FAILED\x10\x03\x12\x17\n\x13TEST_STATUS_ABORTED\x10\x04\x12\x15\n\x11TEST_STATUS_ERROR\x10\x05\x12\x1b\n\x17TEST_STATUS_IN_PROGRESS\x10\x06\x12\x17\n\x13TEST_STATUS_SKIPPED\x10\x07*\xa1\x01\n\x0cTestStepType\x12\x1e\n\x1aTEST_STEP_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TEST_STEP_TYPE_SEQUENCE\x10\x01\x12\x18\n\x14TEST_STEP_TYPE_GROUP\x10\x02\x12\x19\n\x15TEST_STEP_TYPE_ACTION\x10\x03\x12\x1f\n\x1bTEST_STEP_TYPE_FLOW_CONTROL\x10\x04*\xc4\x01\n\x13TestMeasurementType\x12%\n!TEST_MEASUREMENT_TYPE_UNSPECIFIED\x10\x00\x12 \n\x1cTEST_MEASUREMENT_TYPE_DOUBLE\x10\x01\x12 \n\x1cTEST_MEASUREMENT_TYPE_STRING\x10\x03\x12!\n\x1dTEST_MEASUREMENT_TYPE_BOOLEAN\x10\x04\x12\x1f\n\x1bTEST_MEASUREMENT_TYPE_LIMIT\x10\x05\x32\xd7\x1c\n\x11TestReportService\x12\xe4\x01\n\x10ImportTestReport\x12-.sift.test_reports.v1.ImportTestReportRequest\x1a..sift.test_reports.v1.ImportTestReportResponse\"q\x92\x41H\x12\x10ImportTestReport\x1a\x34Imports a test report from an already-uploaded file.\x82\xd3\xe4\x93\x02 \"\x1b/api/v1/test-reports:import:\x01*\x12\xbe\x01\n\x10\x43reateTestReport\x12-.sift.test_reports.v1.CreateTestReportRequest\x1a..sift.test_reports.v1.CreateTestReportResponse\"K\x92\x41)\x12\x10\x43reateTestReport\x1a\x15\x43reates a test report\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/test-reports:\x01*\x12\xc4\x01\n\rGetTestReport\x12*.sift.test_reports.v1.GetTestReportRequest\x1a+.sift.test_reports.v1.GetTestReportResponse\"Z\x92\x41*\x12\rGetTestReport\x1a\x19Gets a single test report\x82\xd3\xe4\x93\x02\'\x12%/api/v1/test-reports/{test_report_id}\x12\xcc\x01\n\x0fListTestReports\x12,.sift.test_reports.v1.ListTestReportsRequest\x1a-.sift.test_reports.v1.ListTestReportsResponse\"\\\x92\x41=\x12\x0fListTestReports\x1a*Lists test reports with optional filtering\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/test-reports\x12\xbe\x01\n\x10UpdateTestReport\x12-.sift.test_reports.v1.UpdateTestReportRequest\x1a..sift.test_reports.v1.UpdateTestReportResponse\"K\x92\x41)\x12\x10UpdateTestReport\x1a\x15Updates a test report\x82\xd3\xe4\x93\x02\x19\x32\x14/api/v1/test-reports:\x01*\x12\xcc\x01\n\x10\x44\x65leteTestReport\x12-.sift.test_reports.v1.DeleteTestReportRequest\x1a..sift.test_reports.v1.DeleteTestReportResponse\"Y\x92\x41)\x12\x10\x44\x65leteTestReport\x1a\x15\x44\x65letes a test report\x82\xd3\xe4\x93\x02\'*%/api/v1/test-reports/{test_report_id}\x12\xb2\x01\n\x0e\x43reateTestStep\x12+.sift.test_reports.v1.CreateTestStepRequest\x1a,.sift.test_reports.v1.CreateTestStepResponse\"E\x92\x41%\x12\x0e\x43reateTestStep\x1a\x13\x43reates a test step\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/test-steps:\x01*\x12\xc0\x01\n\rListTestSteps\x12*.sift.test_reports.v1.ListTestStepsRequest\x1a+.sift.test_reports.v1.ListTestStepsResponse\"V\x92\x41\x39\x12\rListTestSteps\x1a(Lists test steps with optional filtering\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/test-steps\x12\xb2\x01\n\x0eUpdateTestStep\x12+.sift.test_reports.v1.UpdateTestStepRequest\x1a,.sift.test_reports.v1.UpdateTestStepResponse\"E\x92\x41%\x12\x0eUpdateTestStep\x1a\x13Updates a test step\x82\xd3\xe4\x93\x02\x17\x32\x12/api/v1/test-steps:\x01*\x12\xbe\x01\n\x0e\x44\x65leteTestStep\x12+.sift.test_reports.v1.DeleteTestStepRequest\x1a,.sift.test_reports.v1.DeleteTestStepResponse\"Q\x92\x41%\x12\x0e\x44\x65leteTestStep\x1a\x13\x44\x65letes a test step\x82\xd3\xe4\x93\x02#*!/api/v1/test-steps/{test_step_id}\x12\xdc\x01\n\x15\x43reateTestMeasurement\x12\x32.sift.test_reports.v1.CreateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.CreateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15\x43reateTestMeasurement\x1a\x1a\x43reates a test measurement\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/test-measurements:\x01*\x12\x82\x02\n\x16\x43reateTestMeasurements\x12\x33.sift.test_reports.v1.CreateTestMeasurementsRequest\x1a\x34.sift.test_reports.v1.CreateTestMeasurementsResponse\"}\x92\x41P\x12\x16\x43reateTestMeasurements\x1a\x36\x43reates multiple test measurements in a single request\x82\xd3\xe4\x93\x02$\"\x1f/api/v1/test-measurements:batch:\x01*\x12\xea\x01\n\x14ListTestMeasurements\x12\x31.sift.test_reports.v1.ListTestMeasurementsRequest\x1a\x32.sift.test_reports.v1.ListTestMeasurementsResponse\"k\x92\x41G\x12\x14ListTestMeasurements\x1a/Lists test measurements with optional filtering\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/test-measurements\x12\xcb\x01\n\x0e\x43ountTestSteps\x12+.sift.test_reports.v1.CountTestStepsRequest\x1a,.sift.test_reports.v1.CountTestStepsResponse\"^\x92\x41;\x12\x0e\x43ountTestSteps\x1a)Counts test steps with optional filtering\x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/test-steps/count\x12\xf5\x01\n\x15\x43ountTestMeasurements\x12\x32.sift.test_reports.v1.CountTestMeasurementsRequest\x1a\x33.sift.test_reports.v1.CountTestMeasurementsResponse\"s\x92\x41I\x12\x15\x43ountTestMeasurements\x1a\x30\x43ounts test measurements with optional filtering\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/test-measurements/count\x12\xdc\x01\n\x15UpdateTestMeasurement\x12\x32.sift.test_reports.v1.UpdateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.UpdateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15UpdateTestMeasurement\x1a\x1aUpdates a test measurement\x82\xd3\xe4\x93\x02\x1e\x32\x19/api/v1/test-measurements:\x01*\x12\xea\x01\n\x15\x44\x65leteTestMeasurement\x12\x32.sift.test_reports.v1.DeleteTestMeasurementRequest\x1a\x33.sift.test_reports.v1.DeleteTestMeasurementResponse\"h\x92\x41\x33\x12\x15\x44\x65leteTestMeasurement\x1a\x1a\x44\x65letes a test measurement\x82\xd3\xe4\x93\x02,**/api/v1/test-measurements/{measurement_id}\x1a#\x92\x41 \x12\x1eService to manage test reportsB\xb5\x01\n\x18\x63om.sift.test_reports.v1B\x10TestReportsProtoP\x01\xa2\x02\x03STX\xaa\x02\x13Sift.TestReports.V1\xca\x02\x13Sift\\TestReports\\V1\xe2\x02\x1fSift\\TestReports\\V1\\GPBMetadata\xea\x02\x15Sift::TestReports::V1\x92\x41\x18\x12\x16\n\x14Test Results Serviceb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'sift/test_reports/v1/test_reports.proto\x12\x14sift.test_reports.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x1fsift/metadata/v1/metadata.proto\x1a\x17sift/unit/v2/unit.proto\"\xa4\x05\n\nTestReport\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12=\n\x06status\x18\x02 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x08 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\t \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\n \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x44\n\rarchived_date\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x01R\x0c\x61rchivedDate\x12$\n\x0bis_archived\x18\r \x01(\x08\x42\x03\xe0\x41\x01R\nisArchived\x12\x1a\n\x06run_id\x18\x0e \x01(\tB\x03\xe0\x41\x01R\x05runId\"\xaf\x04\n\x08TestStep\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12)\n\x0eparent_step_id\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x0cparentStepId\x12\x17\n\x04name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x03\xe0\x41\x01R\x0b\x64\x65scription\x12\x44\n\tstep_type\x18\x06 \x01(\x0e\x32\".sift.test_reports.v1.TestStepTypeB\x03\xe0\x41\x02R\x08stepType\x12 \n\tstep_path\x18\x07 \x01(\tB\x03\xe0\x41\x02R\x08stepPath\x12=\n\x06status\x18\x08 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12>\n\nstart_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12\x43\n\nerror_info\x18\x0b \x01(\x0b\x32\x1f.sift.test_reports.v1.ErrorInfoB\x03\xe0\x41\x01R\terrorInfo\"Y\n\tErrorInfo\x12\"\n\nerror_code\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\terrorCode\x12(\n\rerror_message\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0c\x65rrorMessage\"\xab\x05\n\x0fTestMeasurement\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\x12Y\n\x10measurement_type\x18\x02 \x01(\x0e\x32).sift.test_reports.v1.TestMeasurementTypeB\x03\xe0\x41\x02R\x0fmeasurementType\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ctest_step_id\x18\x04 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12%\n\rnumeric_value\x18\x06 \x01(\x01H\x00R\x0cnumericValue\x12#\n\x0cstring_value\x18\x07 \x01(\tH\x00R\x0bstringValue\x12%\n\rboolean_value\x18\x08 \x01(\x08H\x00R\x0c\x62ooleanValue\x12+\n\x04unit\x18\t \x01(\x0b\x32\x12.sift.unit.v2.UnitB\x03\xe0\x41\x01R\x04unit\x12L\n\x0enumeric_bounds\x18\n \x01(\x0b\x32#.sift.test_reports.v1.NumericBoundsH\x01R\rnumericBounds\x12I\n\rstring_bounds\x18\x0b \x01(\x0b\x32\".sift.test_reports.v1.StringBoundsH\x01R\x0cstringBounds\x12\x1b\n\x06passed\x18\x0c \x01(\x08\x42\x03\xe0\x41\x02R\x06passed\x12=\n\ttimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\ttimestampB\x07\n\x05valueB\x08\n\x06\x62ounds\"W\n\rNumericBounds\x12\x1a\n\x03min\x18\x01 \x01(\x01\x42\x03\xe0\x41\x01H\x00R\x03min\x88\x01\x01\x12\x1a\n\x03max\x18\x02 \x01(\x01\x42\x03\xe0\x41\x01H\x01R\x03max\x88\x01\x01\x42\x06\n\x04_minB\x06\n\x04_max\":\n\x0cStringBounds\x12*\n\x0e\x65xpected_value\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rexpectedValue\"D\n\x17ImportTestReportRequest\x12)\n\x0eremote_file_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0cremoteFileId\"]\n\x18ImportTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\x9a\x04\n\x17\x43reateTestReportRequest\x12=\n\x06status\x18\x01 \x01(\x0e\x32 .sift.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x07 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\x08 \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\t \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\n \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x1a\n\x06run_id\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x05runId\"]\n\x18\x43reateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"A\n\x14GetTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"Z\n\x15GetTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"\x9b\x01\n\x16ListTestReportsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x86\x01\n\x17ListTestReportsResponse\x12\x43\n\x0ctest_reports\x18\x01 \x03(\x0b\x32 .sift.test_reports.v1.TestReportR\x0btestReports\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa3\x01\n\x17UpdateTestReportRequest\x12\x46\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportB\x03\xe0\x41\x02R\ntestReport\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"]\n\x18UpdateTestReportResponse\x12\x41\n\x0btest_report\x18\x01 \x01(\x0b\x32 .sift.test_reports.v1.TestReportR\ntestReport\"D\n\x17\x44\x65leteTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"\x1a\n\x18\x44\x65leteTestReportResponse\"Y\n\x15\x43reateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\"U\n\x16\x43reateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\"\x99\x01\n\x14ListTestStepsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"~\n\x15ListTestStepsResponse\x12=\n\ntest_steps\x18\x01 \x03(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\ttestSteps\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x9b\x01\n\x15UpdateTestStepRequest\x12@\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"U\n\x16UpdateTestStepResponse\x12;\n\ttest_step\x18\x01 \x01(\x0b\x32\x1e.sift.test_reports.v1.TestStepR\x08testStep\">\n\x15\x44\x65leteTestStepRequest\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\"\x18\n\x16\x44\x65leteTestStepResponse\"u\n\x1c\x43reateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\"q\n\x1d\x43reateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"x\n\x1d\x43reateTestMeasurementsRequest\x12W\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x10testMeasurements\"\x91\x01\n\x1e\x43reateTestMeasurementsResponse\x12\x41\n\x1ameasurements_created_count\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\x18measurementsCreatedCount\x12,\n\x0fmeasurement_ids\x18\x02 \x03(\tB\x03\xe0\x41\x02R\x0emeasurementIds\"\xa0\x01\n\x1bListTestMeasurementsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x9a\x01\n\x1cListTestMeasurementsResponse\x12R\n\x11test_measurements\x18\x01 \x03(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x10testMeasurements\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"4\n\x15\x43ountTestStepsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\".\n\x16\x43ountTestStepsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\";\n\x1c\x43ountTestMeasurementsRequest\x12\x1b\n\x06\x66ilter\x18\x01 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\"5\n\x1d\x43ountTestMeasurementsResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\"\xb7\x01\n\x1cUpdateTestMeasurementRequest\x12U\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"q\n\x1dUpdateTestMeasurementResponse\x12P\n\x10test_measurement\x18\x01 \x01(\x0b\x32%.sift.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"J\n\x1c\x44\x65leteTestMeasurementRequest\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\"\x1f\n\x1d\x44\x65leteTestMeasurementResponse*\xd6\x01\n\nTestStatus\x12\x1b\n\x17TEST_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11TEST_STATUS_DRAFT\x10\x01\x12\x16\n\x12TEST_STATUS_PASSED\x10\x02\x12\x16\n\x12TEST_STATUS_FAILED\x10\x03\x12\x17\n\x13TEST_STATUS_ABORTED\x10\x04\x12\x15\n\x11TEST_STATUS_ERROR\x10\x05\x12\x1b\n\x17TEST_STATUS_IN_PROGRESS\x10\x06\x12\x17\n\x13TEST_STATUS_SKIPPED\x10\x07*\xa1\x01\n\x0cTestStepType\x12\x1e\n\x1aTEST_STEP_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TEST_STEP_TYPE_SEQUENCE\x10\x01\x12\x18\n\x14TEST_STEP_TYPE_GROUP\x10\x02\x12\x19\n\x15TEST_STEP_TYPE_ACTION\x10\x03\x12\x1f\n\x1bTEST_STEP_TYPE_FLOW_CONTROL\x10\x04*\xc4\x01\n\x13TestMeasurementType\x12%\n!TEST_MEASUREMENT_TYPE_UNSPECIFIED\x10\x00\x12 \n\x1cTEST_MEASUREMENT_TYPE_DOUBLE\x10\x01\x12 \n\x1cTEST_MEASUREMENT_TYPE_STRING\x10\x03\x12!\n\x1dTEST_MEASUREMENT_TYPE_BOOLEAN\x10\x04\x12\x1f\n\x1bTEST_MEASUREMENT_TYPE_LIMIT\x10\x05\x32\xd7\x1c\n\x11TestReportService\x12\xe4\x01\n\x10ImportTestReport\x12-.sift.test_reports.v1.ImportTestReportRequest\x1a..sift.test_reports.v1.ImportTestReportResponse\"q\x92\x41H\x12\x10ImportTestReport\x1a\x34Imports a test report from an already-uploaded file.\x82\xd3\xe4\x93\x02 \"\x1b/api/v1/test-reports:import:\x01*\x12\xbe\x01\n\x10\x43reateTestReport\x12-.sift.test_reports.v1.CreateTestReportRequest\x1a..sift.test_reports.v1.CreateTestReportResponse\"K\x92\x41)\x12\x10\x43reateTestReport\x1a\x15\x43reates a test report\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/test-reports:\x01*\x12\xc4\x01\n\rGetTestReport\x12*.sift.test_reports.v1.GetTestReportRequest\x1a+.sift.test_reports.v1.GetTestReportResponse\"Z\x92\x41*\x12\rGetTestReport\x1a\x19Gets a single test report\x82\xd3\xe4\x93\x02\'\x12%/api/v1/test-reports/{test_report_id}\x12\xcc\x01\n\x0fListTestReports\x12,.sift.test_reports.v1.ListTestReportsRequest\x1a-.sift.test_reports.v1.ListTestReportsResponse\"\\\x92\x41=\x12\x0fListTestReports\x1a*Lists test reports with optional filtering\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/test-reports\x12\xbe\x01\n\x10UpdateTestReport\x12-.sift.test_reports.v1.UpdateTestReportRequest\x1a..sift.test_reports.v1.UpdateTestReportResponse\"K\x92\x41)\x12\x10UpdateTestReport\x1a\x15Updates a test report\x82\xd3\xe4\x93\x02\x19\x32\x14/api/v1/test-reports:\x01*\x12\xcc\x01\n\x10\x44\x65leteTestReport\x12-.sift.test_reports.v1.DeleteTestReportRequest\x1a..sift.test_reports.v1.DeleteTestReportResponse\"Y\x92\x41)\x12\x10\x44\x65leteTestReport\x1a\x15\x44\x65letes a test report\x82\xd3\xe4\x93\x02\'*%/api/v1/test-reports/{test_report_id}\x12\xb2\x01\n\x0e\x43reateTestStep\x12+.sift.test_reports.v1.CreateTestStepRequest\x1a,.sift.test_reports.v1.CreateTestStepResponse\"E\x92\x41%\x12\x0e\x43reateTestStep\x1a\x13\x43reates a test step\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/test-steps:\x01*\x12\xc0\x01\n\rListTestSteps\x12*.sift.test_reports.v1.ListTestStepsRequest\x1a+.sift.test_reports.v1.ListTestStepsResponse\"V\x92\x41\x39\x12\rListTestSteps\x1a(Lists test steps with optional filtering\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/test-steps\x12\xb2\x01\n\x0eUpdateTestStep\x12+.sift.test_reports.v1.UpdateTestStepRequest\x1a,.sift.test_reports.v1.UpdateTestStepResponse\"E\x92\x41%\x12\x0eUpdateTestStep\x1a\x13Updates a test step\x82\xd3\xe4\x93\x02\x17\x32\x12/api/v1/test-steps:\x01*\x12\xbe\x01\n\x0e\x44\x65leteTestStep\x12+.sift.test_reports.v1.DeleteTestStepRequest\x1a,.sift.test_reports.v1.DeleteTestStepResponse\"Q\x92\x41%\x12\x0e\x44\x65leteTestStep\x1a\x13\x44\x65letes a test step\x82\xd3\xe4\x93\x02#*!/api/v1/test-steps/{test_step_id}\x12\xdc\x01\n\x15\x43reateTestMeasurement\x12\x32.sift.test_reports.v1.CreateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.CreateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15\x43reateTestMeasurement\x1a\x1a\x43reates a test measurement\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/test-measurements:\x01*\x12\x82\x02\n\x16\x43reateTestMeasurements\x12\x33.sift.test_reports.v1.CreateTestMeasurementsRequest\x1a\x34.sift.test_reports.v1.CreateTestMeasurementsResponse\"}\x92\x41P\x12\x16\x43reateTestMeasurements\x1a\x36\x43reates multiple test measurements in a single request\x82\xd3\xe4\x93\x02$\"\x1f/api/v1/test-measurements:batch:\x01*\x12\xea\x01\n\x14ListTestMeasurements\x12\x31.sift.test_reports.v1.ListTestMeasurementsRequest\x1a\x32.sift.test_reports.v1.ListTestMeasurementsResponse\"k\x92\x41G\x12\x14ListTestMeasurements\x1a/Lists test measurements with optional filtering\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/test-measurements\x12\xcb\x01\n\x0e\x43ountTestSteps\x12+.sift.test_reports.v1.CountTestStepsRequest\x1a,.sift.test_reports.v1.CountTestStepsResponse\"^\x92\x41;\x12\x0e\x43ountTestSteps\x1a)Counts test steps with optional filtering\x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/test-steps/count\x12\xf5\x01\n\x15\x43ountTestMeasurements\x12\x32.sift.test_reports.v1.CountTestMeasurementsRequest\x1a\x33.sift.test_reports.v1.CountTestMeasurementsResponse\"s\x92\x41I\x12\x15\x43ountTestMeasurements\x1a\x30\x43ounts test measurements with optional filtering\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/test-measurements/count\x12\xdc\x01\n\x15UpdateTestMeasurement\x12\x32.sift.test_reports.v1.UpdateTestMeasurementRequest\x1a\x33.sift.test_reports.v1.UpdateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15UpdateTestMeasurement\x1a\x1aUpdates a test measurement\x82\xd3\xe4\x93\x02\x1e\x32\x19/api/v1/test-measurements:\x01*\x12\xea\x01\n\x15\x44\x65leteTestMeasurement\x12\x32.sift.test_reports.v1.DeleteTestMeasurementRequest\x1a\x33.sift.test_reports.v1.DeleteTestMeasurementResponse\"h\x92\x41\x33\x12\x15\x44\x65leteTestMeasurement\x1a\x1a\x44\x65letes a test measurement\x82\xd3\xe4\x93\x02,**/api/v1/test-measurements/{measurement_id}\x1a#\x92\x41 \x12\x1eService to manage test reportsB\xb5\x01\n\x18\x63om.sift.test_reports.v1B\x10TestReportsProtoP\x01\xa2\x02\x03STX\xaa\x02\x13Sift.TestReports.V1\xca\x02\x13Sift\\TestReports\\V1\xe2\x02\x1fSift\\TestReports\\V1\\GPBMetadata\xea\x02\x15Sift::TestReports::V1\x92\x41\x18\x12\x16\n\x14Test Results Serviceb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -127,6 +127,8 @@ _globals['_CREATETESTREPORTREQUEST'].fields_by_name['part_number']._serialized_options = b'\340A\001' _globals['_CREATETESTREPORTREQUEST'].fields_by_name['system_operator']._loaded_options = None _globals['_CREATETESTREPORTREQUEST'].fields_by_name['system_operator']._serialized_options = b'\340A\001' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['run_id']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['run_id']._serialized_options = b'\340A\001' _globals['_GETTESTREPORTREQUEST'].fields_by_name['test_report_id']._loaded_options = None _globals['_GETTESTREPORTREQUEST'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['page_size']._loaded_options = None @@ -221,12 +223,12 @@ _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestMeasurement']._serialized_options = b'\222A3\022\025UpdateTestMeasurement\032\032Updates a test measurement\202\323\344\223\002\0362\031/api/v1/test-measurements:\001*' _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._loaded_options = None _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._serialized_options = b'\222A3\022\025DeleteTestMeasurement\032\032Deletes a test measurement\202\323\344\223\002,**/api/v1/test-measurements/{measurement_id}' - _globals['_TESTSTATUS']._serialized_start=6304 - _globals['_TESTSTATUS']._serialized_end=6518 - _globals['_TESTSTEPTYPE']._serialized_start=6521 - _globals['_TESTSTEPTYPE']._serialized_end=6682 - _globals['_TESTMEASUREMENTTYPE']._serialized_start=6685 - _globals['_TESTMEASUREMENTTYPE']._serialized_end=6881 + _globals['_TESTSTATUS']._serialized_start=6332 + _globals['_TESTSTATUS']._serialized_end=6546 + _globals['_TESTSTEPTYPE']._serialized_start=6549 + _globals['_TESTSTEPTYPE']._serialized_end=6710 + _globals['_TESTMEASUREMENTTYPE']._serialized_start=6713 + _globals['_TESTMEASUREMENTTYPE']._serialized_end=6909 _globals['_TESTREPORT']._serialized_start=302 _globals['_TESTREPORT']._serialized_end=978 _globals['_TESTSTEP']._serialized_start=981 @@ -244,69 +246,69 @@ _globals['_IMPORTTESTREPORTRESPONSE']._serialized_start=2538 _globals['_IMPORTTESTREPORTRESPONSE']._serialized_end=2631 _globals['_CREATETESTREPORTREQUEST']._serialized_start=2634 - _globals['_CREATETESTREPORTREQUEST']._serialized_end=3144 - _globals['_CREATETESTREPORTRESPONSE']._serialized_start=3146 - _globals['_CREATETESTREPORTRESPONSE']._serialized_end=3239 - _globals['_GETTESTREPORTREQUEST']._serialized_start=3241 - _globals['_GETTESTREPORTREQUEST']._serialized_end=3306 - _globals['_GETTESTREPORTRESPONSE']._serialized_start=3308 - _globals['_GETTESTREPORTRESPONSE']._serialized_end=3398 - _globals['_LISTTESTREPORTSREQUEST']._serialized_start=3401 - _globals['_LISTTESTREPORTSREQUEST']._serialized_end=3556 - _globals['_LISTTESTREPORTSRESPONSE']._serialized_start=3559 - _globals['_LISTTESTREPORTSRESPONSE']._serialized_end=3693 - _globals['_UPDATETESTREPORTREQUEST']._serialized_start=3696 - _globals['_UPDATETESTREPORTREQUEST']._serialized_end=3859 - _globals['_UPDATETESTREPORTRESPONSE']._serialized_start=3861 - _globals['_UPDATETESTREPORTRESPONSE']._serialized_end=3954 - _globals['_DELETETESTREPORTREQUEST']._serialized_start=3956 - _globals['_DELETETESTREPORTREQUEST']._serialized_end=4024 - _globals['_DELETETESTREPORTRESPONSE']._serialized_start=4026 - _globals['_DELETETESTREPORTRESPONSE']._serialized_end=4052 - _globals['_CREATETESTSTEPREQUEST']._serialized_start=4054 - _globals['_CREATETESTSTEPREQUEST']._serialized_end=4143 - _globals['_CREATETESTSTEPRESPONSE']._serialized_start=4145 - _globals['_CREATETESTSTEPRESPONSE']._serialized_end=4230 - _globals['_LISTTESTSTEPSREQUEST']._serialized_start=4233 - _globals['_LISTTESTSTEPSREQUEST']._serialized_end=4386 - _globals['_LISTTESTSTEPSRESPONSE']._serialized_start=4388 - _globals['_LISTTESTSTEPSRESPONSE']._serialized_end=4514 - _globals['_UPDATETESTSTEPREQUEST']._serialized_start=4517 - _globals['_UPDATETESTSTEPREQUEST']._serialized_end=4672 - _globals['_UPDATETESTSTEPRESPONSE']._serialized_start=4674 - _globals['_UPDATETESTSTEPRESPONSE']._serialized_end=4759 - _globals['_DELETETESTSTEPREQUEST']._serialized_start=4761 - _globals['_DELETETESTSTEPREQUEST']._serialized_end=4823 - _globals['_DELETETESTSTEPRESPONSE']._serialized_start=4825 - _globals['_DELETETESTSTEPRESPONSE']._serialized_end=4849 - _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_start=4851 - _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_end=4968 - _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_start=4970 - _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_end=5083 - _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_start=5085 - _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_end=5205 - _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_start=5208 - _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_end=5353 - _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_start=5356 - _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_end=5516 - _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_start=5519 - _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_end=5673 - _globals['_COUNTTESTSTEPSREQUEST']._serialized_start=5675 - _globals['_COUNTTESTSTEPSREQUEST']._serialized_end=5727 - _globals['_COUNTTESTSTEPSRESPONSE']._serialized_start=5729 - _globals['_COUNTTESTSTEPSRESPONSE']._serialized_end=5775 - _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_start=5777 - _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_end=5836 - _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_start=5838 - _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_end=5891 - _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_start=5894 - _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_end=6077 - _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_start=6079 - _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_end=6192 - _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_start=6194 - _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_end=6268 - _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_start=6270 - _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_end=6301 - _globals['_TESTREPORTSERVICE']._serialized_start=6884 - _globals['_TESTREPORTSERVICE']._serialized_end=10555 + _globals['_CREATETESTREPORTREQUEST']._serialized_end=3172 + _globals['_CREATETESTREPORTRESPONSE']._serialized_start=3174 + _globals['_CREATETESTREPORTRESPONSE']._serialized_end=3267 + _globals['_GETTESTREPORTREQUEST']._serialized_start=3269 + _globals['_GETTESTREPORTREQUEST']._serialized_end=3334 + _globals['_GETTESTREPORTRESPONSE']._serialized_start=3336 + _globals['_GETTESTREPORTRESPONSE']._serialized_end=3426 + _globals['_LISTTESTREPORTSREQUEST']._serialized_start=3429 + _globals['_LISTTESTREPORTSREQUEST']._serialized_end=3584 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_start=3587 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_end=3721 + _globals['_UPDATETESTREPORTREQUEST']._serialized_start=3724 + _globals['_UPDATETESTREPORTREQUEST']._serialized_end=3887 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_start=3889 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_end=3982 + _globals['_DELETETESTREPORTREQUEST']._serialized_start=3984 + _globals['_DELETETESTREPORTREQUEST']._serialized_end=4052 + _globals['_DELETETESTREPORTRESPONSE']._serialized_start=4054 + _globals['_DELETETESTREPORTRESPONSE']._serialized_end=4080 + _globals['_CREATETESTSTEPREQUEST']._serialized_start=4082 + _globals['_CREATETESTSTEPREQUEST']._serialized_end=4171 + _globals['_CREATETESTSTEPRESPONSE']._serialized_start=4173 + _globals['_CREATETESTSTEPRESPONSE']._serialized_end=4258 + _globals['_LISTTESTSTEPSREQUEST']._serialized_start=4261 + _globals['_LISTTESTSTEPSREQUEST']._serialized_end=4414 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_start=4416 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_end=4542 + _globals['_UPDATETESTSTEPREQUEST']._serialized_start=4545 + _globals['_UPDATETESTSTEPREQUEST']._serialized_end=4700 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_start=4702 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_end=4787 + _globals['_DELETETESTSTEPREQUEST']._serialized_start=4789 + _globals['_DELETETESTSTEPREQUEST']._serialized_end=4851 + _globals['_DELETETESTSTEPRESPONSE']._serialized_start=4853 + _globals['_DELETETESTSTEPRESPONSE']._serialized_end=4877 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_start=4879 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_end=4996 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_start=4998 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_end=5111 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_start=5113 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_end=5233 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_start=5236 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_end=5381 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_start=5384 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_end=5544 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_start=5547 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_end=5701 + _globals['_COUNTTESTSTEPSREQUEST']._serialized_start=5703 + _globals['_COUNTTESTSTEPSREQUEST']._serialized_end=5755 + _globals['_COUNTTESTSTEPSRESPONSE']._serialized_start=5757 + _globals['_COUNTTESTSTEPSRESPONSE']._serialized_end=5803 + _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_start=5805 + _globals['_COUNTTESTMEASUREMENTSREQUEST']._serialized_end=5864 + _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_start=5866 + _globals['_COUNTTESTMEASUREMENTSRESPONSE']._serialized_end=5919 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_start=5922 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_end=6105 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_start=6107 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_end=6220 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_start=6222 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_end=6296 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_start=6298 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_end=6329 + _globals['_TESTREPORTSERVICE']._serialized_start=6912 + _globals['_TESTREPORTSERVICE']._serialized_end=10583 # @@protoc_insertion_point(module_scope) diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.pyi b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi index 354d40623..cde2b0583 100644 --- a/python/lib/sift/test_reports/v1/test_reports_pb2.pyi +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi @@ -413,6 +413,7 @@ class CreateTestReportRequest(google.protobuf.message.Message): SERIAL_NUMBER_FIELD_NUMBER: builtins.int PART_NUMBER_FIELD_NUMBER: builtins.int SYSTEM_OPERATOR_FIELD_NUMBER: builtins.int + RUN_ID_FIELD_NUMBER: builtins.int status: global___TestStatus.ValueType """The status of the test run""" name: builtins.str @@ -427,6 +428,8 @@ class CreateTestReportRequest(google.protobuf.message.Message): """The part number for the DUT""" system_operator: builtins.str """Unique identifier for user owner""" + run_id: builtins.str + """The run ID for the test report""" @property def start_time(self) -> google.protobuf.timestamp_pb2.Timestamp: """The start time of the test run""" @@ -452,9 +455,10 @@ class CreateTestReportRequest(google.protobuf.message.Message): serial_number: builtins.str = ..., part_number: builtins.str = ..., system_operator: builtins.str = ..., + run_id: builtins.str = ..., ) -> None: ... def HasField(self, field_name: typing.Literal["end_time", b"end_time", "start_time", b"start_time"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["end_time", b"end_time", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_system_name", b"test_system_name"]) -> None: ... + def ClearField(self, field_name: typing.Literal["end_time", b"end_time", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "run_id", b"run_id", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_system_name", b"test_system_name"]) -> None: ... global___CreateTestReportRequest = CreateTestReportRequest diff --git a/python/lib/sift_client/_internal/low_level_wrappers/test_results.py b/python/lib/sift_client/_internal/low_level_wrappers/test_results.py index 4f9eafbe3..d0cf8b827 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/test_results.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/test_results.py @@ -3,13 +3,11 @@ import logging from typing import TYPE_CHECKING, Any, cast -from google.protobuf.timestamp_pb2 import Timestamp from sift.test_reports.v1.test_reports_pb2 import ( CreateTestMeasurementRequest, CreateTestMeasurementResponse, CreateTestMeasurementsRequest, CreateTestMeasurementsResponse, - CreateTestReportRequest, CreateTestReportResponse, CreateTestStepRequest, CreateTestStepResponse, @@ -43,13 +41,11 @@ TestReport, TestReportCreate, TestReportUpdate, - TestStatus, TestStep, TestStepCreate, TestStepUpdate, ) from sift_client.transport import WithGrpcClient -from sift_client.util.metadata import metadata_dict_to_proto if TYPE_CHECKING: from sift_client.transport.grpc_transport import GrpcClient @@ -105,37 +101,7 @@ async def create_test_report( Returns: The created TestReport. """ - request_kwargs: dict[str, Any] = { - "status": test_report.status.value - if isinstance(test_report.status, TestStatus) - else test_report.status, - "name": test_report.name, - "test_system_name": test_report.test_system_name, - "test_case": test_report.test_case, - } - - # Handle timestamps - start_ts = Timestamp() - start_ts.FromDatetime(test_report.start_time) - request_kwargs["start_time"] = start_ts - - end_ts = Timestamp() - end_ts.FromDatetime(test_report.end_time) - request_kwargs["end_time"] = end_ts - - if test_report.metadata is not None: - request_kwargs["metadata"] = metadata_dict_to_proto(test_report.metadata) - - if test_report.serial_number is not None: - request_kwargs["serial_number"] = test_report.serial_number - - if test_report.part_number is not None: - request_kwargs["part_number"] = test_report.part_number - - if test_report.system_operator is not None: - request_kwargs["system_operator"] = test_report.system_operator - - request = CreateTestReportRequest(**request_kwargs) + request = test_report.to_proto() response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestReport(request) grpc_test_report = cast("CreateTestReportResponse", response).test_report return TestReport._from_proto(grpc_test_report) @@ -336,7 +302,9 @@ async def update_test_step(self, update: TestStepUpdate) -> TestStep: The updated TestStep. """ test_step_proto, field_mask = update.to_proto_with_mask() - + has_error_info = test_step_proto.HasField("error_info") + if has_error_info: + field_mask.paths.append("error_info") request = UpdateTestStepRequest(test_step=test_step_proto, update_mask=field_mask) response = await self._grpc_client.get_stub(TestReportServiceStub).UpdateTestStep(request) grpc_test_step = cast("UpdateTestStepResponse", response).test_step diff --git a/python/lib/sift_client/_tests/resources/test_test_results.py b/python/lib/sift_client/_tests/resources/test_test_results.py index d3695f6d9..ff4993098 100644 --- a/python/lib/sift_client/_tests/resources/test_test_results.py +++ b/python/lib/sift_client/_tests/resources/test_test_results.py @@ -1,6 +1,5 @@ from __future__ import annotations -import socket from datetime import datetime, timedelta, timezone from pathlib import Path from typing import ClassVar @@ -24,14 +23,8 @@ TestStepType, ) -# from sift_client.util.test_results.pytest import report_context, step -from sift_client.util.test_results.context_manager import NewStep # noqa: F401 - pytestmark = pytest.mark.integration -case_name = Path(__file__).stem -test_system_name = socket.gethostname() - def test_client_binding(sift_client): assert sift_client.test_results @@ -45,7 +38,7 @@ class TestResultsTest: test_steps: ClassVar[dict[str, TestStep]] = {} test_measurements: ClassVar[dict[str, TestMeasurement]] = {} - def test_create_test_report(self, sift_client, step): + def test_create_test_report(self, sift_client, step, nostromo_run): print("STEP", step) # Create a test report simulated_time = datetime.now(timezone.utc) @@ -57,9 +50,11 @@ def test_create_test_report(self, sift_client, step): "test_case": "Test Case", "start_time": simulated_time, "end_time": simulated_time, + "run_id": nostromo_run.id_, }, ) assert test_report.id_ is not None + assert test_report.run_id == nostromo_run.id_ self.test_reports["basic_test_report"] = test_report step.measure(name="test_report_created", value=True) with step.substep(name="nested step") as nested_step: @@ -301,6 +296,7 @@ def test_update_test_report(self, sift_client): "automated": True, }, end_time=new_end_time, + run_id="", ), ) @@ -316,6 +312,7 @@ def test_update_test_report(self, sift_client): } assert updated_report.status == TestStatus.FAILED assert updated_report.end_time == new_end_time + assert updated_report.run_id is None self.test_reports["basic_test_report"] = updated_report diff --git a/python/lib/sift_client/_tests/util/test_test_results_utils.py b/python/lib/sift_client/_tests/util/test_test_results_utils.py index e69de29bb..ae4a230bc 100644 --- a/python/lib/sift_client/_tests/util/test_test_results_utils.py +++ b/python/lib/sift_client/_tests/util/test_test_results_utils.py @@ -0,0 +1,357 @@ +from datetime import datetime, timezone + +import pytest + +from sift_client.sift_types.test_report import ( + TestMeasurementCreate, + TestMeasurementType, + TestMeasurementUpdate, + TestStatus, + TestStepType, +) +from sift_client.util.test_results.bounds import ( + assign_value_to_measurement, + evaluate_measurement_bounds, +) +from sift_client.util.test_results.context_manager import NewStep + +pytestmark = pytest.mark.integration + + +class TestContextManager: + def test_new_step(self, report_context): + initial_end_time = report_context.report.end_time + first_step_path = report_context.get_next_step_path() + substep_path = f"{first_step_path}.1" + nested_substep_path = f"{substep_path}.1" + sibling_substep_path = f"{first_step_path}.2" + sibling_nested_substep_path = f"{substep_path}.2" + first_step_path_parts = first_step_path.split(".") + prefix = "" + if len(first_step_path_parts) > 1: + prefix = f"{'.'.join(first_step_path_parts[:-1])}." + second_step_path = f"{prefix}{int(first_step_path_parts[-1]) + 1}" + test_step = None + with NewStep(report_context, "Test Step", "Test Description") as new_step: + test_step = new_step.current_step + assert test_step.test_report_id == report_context.report.id_ + assert test_step.name == "Test Step" + assert test_step.description == "Test Description" + assert test_step.start_time + assert test_step.end_time + assert test_step.status == TestStatus.IN_PROGRESS + assert test_step.step_path == first_step_path + assert test_step.step_type == TestStepType.ACTION + assert test_step.error_info == None + + with new_step.substep("Substep", "Substep Description") as substep: + current_substep = substep.current_step + assert current_substep.test_report_id == report_context.report.id_ + assert current_substep.name == "Substep" + assert current_substep.description == "Substep Description" + assert current_substep.step_path == substep_path + assert current_substep.parent_step_id == test_step.id_ + assert current_substep.start_time + assert current_substep.end_time + assert current_substep.status == TestStatus.IN_PROGRESS + assert current_substep.step_type == TestStepType.ACTION + assert current_substep.error_info == None + + with substep.substep( + "nested substep", "Nested substep Description" + ) as nested_substep: + current_nested_substep = nested_substep.current_step + assert current_nested_substep.test_report_id == report_context.report.id_ + assert current_nested_substep.name == "nested substep" + assert current_nested_substep.description == "Nested substep Description" + assert current_nested_substep.step_path == nested_substep_path + assert current_nested_substep.parent_step_id == current_substep.id_ + assert current_nested_substep.start_time + assert current_nested_substep.end_time + assert current_nested_substep.parent_step_id == current_substep.id_ + + with substep.substep( + "sibling nested substep", "Sibling nested substep Description" + ) as sibling_substep: + current_sibling_substep = sibling_substep.current_step + assert current_sibling_substep.test_report_id == report_context.report.id_ + assert current_sibling_substep.name == "sibling nested substep" + assert ( + current_sibling_substep.description == "Sibling nested substep Description" + ) + assert current_sibling_substep.step_path == sibling_nested_substep_path + assert current_sibling_substep.parent_step_id == current_substep.id_ + + with new_step.substep( + "sibling substep", "Sibling substep Description" + ) as sibling_substep: + current_sibling_substep = sibling_substep.current_step + assert current_sibling_substep.test_report_id == report_context.report.id_ + assert current_sibling_substep.name == "sibling substep" + assert current_sibling_substep.description == "Sibling substep Description" + assert current_sibling_substep.step_path == sibling_substep_path + assert current_sibling_substep.parent_step_id == test_step.id_ + + with report_context.new_step("Test Step 2", "Test Step 2 Description") as new_step_2: + test_step_2 = new_step_2.current_step + assert test_step_2.test_report_id == report_context.report.id_ + assert test_step_2.name == "Test Step 2" + assert test_step_2.description == "Test Step 2 Description" + assert test_step_2.step_path == second_step_path + + assert test_step.end_time > initial_end_time + assert test_step_2.start_time > test_step.end_time + assert test_step.status == TestStatus.PASSED + + def test_measurement_update(self, report_context): + test_step = None + with report_context.new_step("Test Measure", "Test Measure Description") as new_step: + test_step = new_step.current_step + new_step.measure(name="Test Measurement", value=10, bounds={"min": 0, "max": 10}) + new_step.measure(name="Test Measurement 2", value="string value", bounds="string value") + new_step.measure(name="Test Measurement 3", value=True, bounds="true") + + assert len(test_step.measurements) == 3 + assert test_step.measurements[0].name == "Test Measurement" + assert test_step.measurements[0].numeric_value == 10 + assert test_step.measurements[0].measurement_type == TestMeasurementType.DOUBLE + assert test_step.measurements[1].name == "Test Measurement 2" + assert test_step.measurements[1].string_value == "string value" + assert test_step.measurements[1].measurement_type == TestMeasurementType.STRING + assert test_step.measurements[2].name == "Test Measurement 3" + assert test_step.measurements[2].boolean_value == True + assert test_step.measurements[2].measurement_type == TestMeasurementType.BOOLEAN + + def test_error_info(self, report_context, step): + test_step = None + parent_step_path = step.current_step.step_path + open_step_result = report_context.open_step_results.get(parent_step_path, False) + any_failures = report_context.any_failures + + with report_context.new_step("Test Error", "Test Error Description") as new_step: + test_step = new_step.current_step + raise Exception("Test Error") + assert test_step.error_info is not None + assert test_step.error_info.error_code == 1 + assert test_step.error_info.error_message == "Test Error" + assert test_step.status == TestStatus.ERROR + # If the parent step is not marked as failed already, make sure it remains passed at this point. + if not open_step_result: + report_context.open_step_results[parent_step_path] = True + if not any_failures: + report_context.any_failures = False + + +class TestBounds: + def test_assign_value_to_measurement(self): + measurement = TestMeasurementUpdate( + measurement_type=TestMeasurementType.DOUBLE, + ) + assign_value_to_measurement(measurement, 10) + assert measurement.numeric_value == 10 + assert measurement.measurement_type == TestMeasurementType.DOUBLE + measurement = TestMeasurementCreate( + test_step_id="test_step_id", + name="Test Measurement", + passed=True, + timestamp=datetime.now(timezone.utc), + ) + assign_value_to_measurement(measurement, "string value") + assert measurement.string_value == "string value" + assert measurement.measurement_type == TestMeasurementType.STRING + measurement = TestMeasurementUpdate() + assign_value_to_measurement(measurement, True) + assert measurement.boolean_value == True + + def test_evaluate_measurement_bounds(self): + measurement = TestMeasurementUpdate( + measurement_type=TestMeasurementType.DOUBLE, + ) + result = evaluate_measurement_bounds(measurement, 10, {"min": 0, "max": 10}) + assert result == True + assert measurement.passed == True + + def test_evaluate_measurement_bounds_numeric_within_range(self): + """Test numeric value within min and max bounds.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 5.0, {"min": 0.0, "max": 10.0}) + assert result == True + assert measurement.passed == True + assert measurement.numeric_value == 5.0 + assert measurement.measurement_type == TestMeasurementType.DOUBLE + + def test_evaluate_measurement_bounds_numeric_at_min(self): + """Test numeric value exactly at minimum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 0.0, {"min": 0.0, "max": 10.0}) + assert result == True + assert measurement.passed == True + + def test_evaluate_measurement_bounds_numeric_at_max(self): + """Test numeric value exactly at maximum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 10.0, {"min": 0.0, "max": 10.0}) + assert result == True + assert measurement.passed == True + + def test_evaluate_measurement_bounds_numeric_below_min(self): + """Test numeric value below minimum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, -1.0, {"min": 0.0, "max": 10.0}) + assert result == False + assert measurement.passed == False + + def test_evaluate_measurement_bounds_numeric_above_max(self): + """Test numeric value above maximum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 11.0, {"min": 0.0, "max": 10.0}) + assert result == False + assert measurement.passed == False + + def test_evaluate_measurement_bounds_numeric_min_only(self): + """Test numeric value with only minimum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 5.0, {"min": 0.0}) + assert result == True + assert measurement.passed == True + + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, -1.0, {"min": 0.0}) + assert result == False + assert measurement.passed == False + + def test_evaluate_measurement_bounds_numeric_max_only(self): + """Test numeric value with only maximum bound.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 5.0, {"max": 10.0}) + assert result == True + assert measurement.passed == True + + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 11.0, {"max": 10.0}) + assert result == False + assert measurement.passed == False + + def test_evaluate_measurement_bounds_with_numeric_bounds_object(self): + """Test using NumericBounds object instead of dict.""" + from sift_client.sift_types.test_report import NumericBounds + + measurement = TestMeasurementUpdate() + bounds = NumericBounds(min=0.0, max=10.0) + result = evaluate_measurement_bounds(measurement, 5.0, bounds) + assert result == True + assert measurement.passed == True + assert measurement.numeric_bounds.min == 0.0 + assert measurement.numeric_bounds.max == 10.0 + + def test_evaluate_measurement_bounds_string_matching(self): + """Test string value matching expected string.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, "expected", "expected") + assert result == True + assert measurement.passed == True + assert measurement.string_value == "expected" + assert measurement.string_expected_value == "expected" + assert measurement.measurement_type == TestMeasurementType.STRING + + def test_evaluate_measurement_bounds_string_not_matching(self): + """Test string value not matching expected string.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, "actual", "expected") + assert result == False + assert measurement.passed == False + assert measurement.string_value == "actual" + assert measurement.string_expected_value == "expected" + + def test_evaluate_measurement_bounds_boolean_matching(self): + """Test boolean value matching expected string.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, True, "true") + assert result == True + assert measurement.passed == True + assert measurement.boolean_value == True + assert measurement.string_expected_value == "true" + assert measurement.measurement_type == TestMeasurementType.BOOLEAN + + def test_evaluate_measurement_bounds_boolean_not_matching(self): + """Test boolean value not matching expected string.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, False, "true") + assert result == False + assert measurement.passed == False + assert measurement.boolean_value == False + assert measurement.string_expected_value == "true" + + def test_evaluate_measurement_bounds_boolean_case_insensitive(self): + """Test boolean comparison is case insensitive.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, True, "TRUE") + assert result == True + assert measurement.passed == True + + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, True, "True") + assert result == True + assert measurement.passed == True + + def test_evaluate_measurement_bounds_no_bounds(self): + """Test measurement without bounds. Expected behavior is that the measurement's passed value is unchanged.""" + measurement = TestMeasurementUpdate(passed=False) + result = evaluate_measurement_bounds(measurement, 5.0, None) + assert result == False # + assert measurement.passed == False + assert measurement.numeric_value == 5.0 + + measurement = TestMeasurementUpdate(passed=True) + result = evaluate_measurement_bounds(measurement, "string value", None) + assert result == True + assert measurement.passed == True + assert measurement.string_value == "string value" + + def test_evaluate_measurement_bounds_integer_value(self): + """Test that integer values are converted to float.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 5, {"min": 0, "max": 10}) + assert result == True + assert measurement.passed == True + assert measurement.numeric_value == 5.0 + assert measurement.measurement_type == TestMeasurementType.DOUBLE + + def test_evaluate_measurement_bounds_with_test_measurement_create(self): + """Test evaluation with TestMeasurementCreate.""" + measurement = TestMeasurementCreate( + test_step_id="test_step_id", + name="Test Measurement", + passed=True, + timestamp=datetime.now(timezone.utc), + ) + result = evaluate_measurement_bounds(measurement, 5.0, {"min": 0.0, "max": 10.0}) + assert result == True + assert measurement.passed == True + assert measurement.numeric_value == 5.0 + + def test_evaluate_measurement_bounds_negative_range(self): + """Test numeric bounds with negative values.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, -5.0, {"min": -10.0, "max": -1.0}) + assert result == True + assert measurement.passed == True + + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 0.0, {"min": -10.0, "max": -1.0}) + assert result == False + assert measurement.passed == False + + def test_evaluate_measurement_bounds_large_values(self): + """Test numeric bounds with large values.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 1e6, {"min": 0.0, "max": 1e9}) + assert result == True + assert measurement.passed == True + + def test_evaluate_measurement_bounds_small_precision(self): + """Test numeric bounds with small decimal precision.""" + measurement = TestMeasurementUpdate() + result = evaluate_measurement_bounds(measurement, 0.00001, {"min": 0.0, "max": 0.0001}) + assert result == True + assert measurement.passed == True diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py index c8006ef8f..9f0d3c6c4 100644 --- a/python/lib/sift_client/sift_types/test_report.py +++ b/python/lib/sift_client/sift_types/test_report.py @@ -4,6 +4,9 @@ from enum import Enum from typing import TYPE_CHECKING, ClassVar +from sift.test_reports.v1.test_reports_pb2 import ( + CreateTestReportRequest as CreateTestReportRequestProto, +) from sift.test_reports.v1.test_reports_pb2 import ( ErrorInfo as ErrorInfoProto, ) @@ -454,7 +457,7 @@ def _add_resource_id_to_proto(self, proto_msg: TestReportProto): proto_msg.test_report_id = self._resource_id -class TestReportCreate(TestReportBase, ModelCreate[TestReportProto]): +class TestReportCreate(TestReportBase, ModelCreate[CreateTestReportRequestProto]): """Create model for TestReport.""" name: str @@ -463,29 +466,23 @@ class TestReportCreate(TestReportBase, ModelCreate[TestReportProto]): start_time: datetime end_time: datetime - def to_proto(self) -> TestReportProto: + def to_proto(self) -> CreateTestReportRequestProto: """Convert to protobuf message with custom logic.""" - proto = TestReportProto( + proto = CreateTestReportRequestProto( status=self.status.value, # type: ignore name=self.name, test_system_name=self.test_system_name, test_case=self.test_case, + serial_number=self.serial_number, + part_number=self.part_number, + system_operator=self.system_operator, + run_id=self.run_id, metadata=metadata_dict_to_proto(self.metadata) if self.metadata else {}, - is_archived=False, ) proto.start_time.FromDatetime(self.start_time) proto.end_time.FromDatetime(self.end_time) - if self.serial_number: - proto.serial_number = self.serial_number - - if self.part_number: - proto.part_number = self.part_number - - if self.system_operator: - proto.system_operator = self.system_operator - return proto @@ -526,6 +523,7 @@ class TestReport(BaseType[TestReportProto, "TestReport"]): serial_number: str | None = None part_number: str | None = None system_operator: str | None = None + run_id: str | None = None archived_date: datetime | None = None is_archived: bool @@ -546,6 +544,7 @@ def _from_proto( serial_number=proto.serial_number if proto.serial_number else None, part_number=proto.part_number if proto.part_number else None, system_operator=proto.system_operator if proto.system_operator else None, + run_id=proto.run_id if proto.run_id else None, archived_date=proto.archived_date.ToDatetime(tzinfo=timezone.utc) if proto.HasField("archived_date") else None, @@ -577,6 +576,9 @@ def _to_proto(self) -> TestReportProto: if self.system_operator: proto.system_operator = self.system_operator + if self.run_id: + proto.run_id = self.run_id + if self.archived_date: proto.archived_date.FromDatetime(self.archived_date) diff --git a/python/lib/sift_client/util/test_results/bounds.py b/python/lib/sift_client/util/test_results/bounds.py index e8fa97903..bb74386c1 100644 --- a/python/lib/sift_client/util/test_results/bounds.py +++ b/python/lib/sift_client/util/test_results/bounds.py @@ -4,6 +4,7 @@ NumericBounds, TestMeasurement, TestMeasurementCreate, + TestMeasurementType, TestMeasurementUpdate, ) @@ -13,12 +14,15 @@ def assign_value_to_measurement( value: float | str | bool, ) -> None: """Assign the resolved value type to a measurement.""" - if isinstance(value, float): - measurement.numeric_value = value + if isinstance(value, bool): + measurement.boolean_value = value + measurement.measurement_type = TestMeasurementType.BOOLEAN + elif isinstance(value, float) or isinstance(value, int): + measurement.numeric_value = float(value) + measurement.measurement_type = TestMeasurementType.DOUBLE elif isinstance(value, str): measurement.string_value = value - elif isinstance(value, bool): - measurement.boolean_value = value + measurement.measurement_type = TestMeasurementType.STRING else: raise ValueError("Invalid value type") @@ -52,6 +56,7 @@ def evaluate_measurement_bounds( measurement.passed = value == bounds elif isinstance(bounds, NumericBounds): measurement.numeric_bounds = bounds + measurement.passed = True float_value = float(value) if measurement.numeric_bounds.min is not None: measurement.passed = ( diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index ff553ce94..e5bb9f84a 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING from sift_client.sift_types.test_report import ( + ErrorInfo, NumericBounds, TestMeasurementCreate, TestReport, @@ -69,7 +70,6 @@ def create( Returns: The new report context. """ - print(f"Creating new report context: {name} {test_system_name} {test_case}") test_case = test_case if test_case else os.path.basename(__file__) test_system_name = test_system_name if test_system_name else socket.gethostname() system_operator = system_operator if system_operator else getpass.getuser() @@ -126,12 +126,17 @@ def create_step(self, name: str, description: str | None = None) -> TestStep: return step def resolve_and_propagate_step_result( - self, step: TestStep, parent_step: TestStep | None = None + self, + step: TestStep, + parent_step: TestStep | None = None, + error_info: ErrorInfo | None = None, ) -> bool: """Resolve the result of a step and propagate the result to the parent step if it failed.""" result = self.open_step_results.get(step.step_path, True) + if error_info: + result = False if step.status != TestStatus.IN_PROGRESS: - # The step was not manually completed so use that. + # The step was manually completed so use that result. result = step.status == TestStatus.PASSED # Update the parent step results if this step failed (true by default so no need to do anything if we didn't fail). @@ -140,14 +145,13 @@ def resolve_and_propagate_step_result( if parent_step: self.open_step_results[parent_step.step_path] = False - # Cleanup the open step results for this step. - self.open_step_results.pop(step.step_path) return result def exit_step(self, step: TestStep): """Exit a step and update the report context.""" self.step_number_at_depth[len(self.step_stack)] = 0 stack_top = self.step_stack.pop() + self.open_step_results.pop(step.step_path) if stack_top.id_ != step.id_: raise ValueError( @@ -188,33 +192,40 @@ def __enter__(self): returns: The current step. """ - print(f"Creating step {self.name} for report {self.report_context.report.id_}") self.current_step = self.report_context.create_step(self.name, self.description) self.name = self.current_step.name return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc, exc_value, traceback): + error_info = None + if exc: + error_info = ErrorInfo( + error_code=1, + error_message=str(exc_value), + ) + print("Captured exception: ", exc_value) + print("error info: ", error_info) result = self.report_context.resolve_and_propagate_step_result( - self.current_step, self.parent_step + self.current_step, self.parent_step, error_info ) # Mark the step as completed + status = TestStatus.PASSED if result else TestStatus.FAILED + if error_info: + status = TestStatus.ERROR self.current_step.update( { - "status": TestStatus.PASSED if result else TestStatus.FAILED, + "status": status, "end_time": datetime.now(timezone.utc), + "error_info": error_info, } ) - print( - "Leaving step", - self.current_step.step_path, - self.current_step.name, - self.current_step.test_report_id, - ) # Update the last step to the parent. self.report_context.exit_step(self.current_step) + return True + def measure( self, *, diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 60b5e54ca..432299936 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -6,6 +6,7 @@ import pytest +from sift_client.sift_types.test_report import TestStatus from sift_client.util.test_results import ReportContext if TYPE_CHECKING: @@ -26,7 +27,9 @@ def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> R "end_time": datetime.now(timezone.utc), } if context.any_failures: - update["status"] = 3 # TestStatus.FAILED + update["status"] = TestStatus.FAILED + else: + update["status"] = TestStatus.PASSED context.report.update(update) From cb87f94f174913956233ee31fe378a6f91dff07f Mon Sep 17 00:00:00 2001 From: Ian Later Date: Thu, 23 Oct 2025 20:13:03 -0700 Subject: [PATCH 06/16] make report context a context manager --- .../_tests/util/test_test_results_utils.py | 1 + .../util/test_results/context_manager.py | 53 ++++++++++--------- .../sift_client/util/test_results/pytest.py | 15 ++---- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/python/lib/sift_client/_tests/util/test_test_results_utils.py b/python/lib/sift_client/_tests/util/test_test_results_utils.py index ae4a230bc..f9722ea21 100644 --- a/python/lib/sift_client/_tests/util/test_test_results_utils.py +++ b/python/lib/sift_client/_tests/util/test_test_results_utils.py @@ -32,6 +32,7 @@ def test_new_step(self, report_context): prefix = f"{'.'.join(first_step_path_parts[:-1])}." second_step_path = f"{prefix}{int(first_step_path_parts[-1]) + 1}" test_step = None + # Test NewStep as a context manager directly with NewStep(report_context, "Test Step", "Test Description") as new_step: test_step = new_step.current_step assert test_step.test_report_id == report_context.report.id_ diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index e5bb9f84a..2a3481d02 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -26,8 +26,8 @@ from sift_client.client import SiftClient -class ReportContext: - """Context for a new TestReport. Is not itself a context manager but serves as a store to communicate between step context managers since they can be nested or siblings.""" +class ReportContext(AbstractContextManager): + """Context for a new TestReport.""" report: TestReport step_is_open: bool @@ -36,29 +36,15 @@ class ReportContext: open_step_results: dict[str, bool] any_failures: bool - def __init__(self, report: TestReport): - """Initialize a new report context. - - Args: - report: The report to create the context for. - """ - self.report = report - self.step_is_open = False - self.step_stack = [] - self.step_number_at_depth = {} - self.open_step_results = {} - self.any_failures = False - - @classmethod - def create( - cls, + def __init__( + self, client: SiftClient, name: str, test_system_name: str | None = None, system_operator: str | None = None, test_case: str | None = None, - ) -> ReportContext: - """Create a new report context. + ): + """Initialize a new report context. Args: client: The Sift client to use to create the report. @@ -66,10 +52,14 @@ def create( test_system_name: The name of the test system. Will default to the hostname if not provided. system_operator: The operator of the test system. Will default to the current user if not provided. test_case: The name of the test case. Will default to the basename of the file containing the test if not provided. - - Returns: - The new report context. """ + self.step_is_open = False + self.step_stack = [] + self.step_number_at_depth = {} + self.open_step_results = {} + self.any_failures = False + + # Create the report. test_case = test_case if test_case else os.path.basename(__file__) test_system_name = test_system_name if test_system_name else socket.gethostname() system_operator = system_operator if system_operator else getpass.getuser() @@ -82,8 +72,21 @@ def create( status=TestStatus.IN_PROGRESS, system_operator=system_operator, ) - report = client.test_results.create(create) - return cls(report) + self.report = client.test_results.create(create) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + update = { + "end_time": datetime.now(timezone.utc), + } + if self.any_failures or exc_type: + update["status"] = TestStatus.FAILED + else: + update["status"] = TestStatus.PASSED + self.report.update(update) + return True def new_step(self, name: str, description: str | None = None) -> NewStep: """Alias to return a new step context manager from this report context. Use create_step for actually creating a TestStep in the current context.""" diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 432299936..fe9be9aff 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -6,7 +6,6 @@ import pytest -from sift_client.sift_types.test_report import TestStatus from sift_client.util.test_results import ReportContext if TYPE_CHECKING: @@ -17,20 +16,12 @@ def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> ReportContext: """Create a report context for the session.""" test_path = Path(request.config.invocation_params.args[0]) - context = ReportContext.create( + with ReportContext( sift_client, name=f"{test_path.name} {datetime.now(timezone.utc).isoformat()}", test_case=str(test_path), - ) - yield context - update = { - "end_time": datetime.now(timezone.utc), - } - if context.any_failures: - update["status"] = TestStatus.FAILED - else: - update["status"] = TestStatus.PASSED - context.report.update(update) + ) as context: + yield context @pytest.fixture(autouse=True) From bc7a95f2bc2d6bf9243fc17b04b2415011504f0f Mon Sep 17 00:00:00 2001 From: Ian Later Date: Thu, 23 Oct 2025 20:17:37 -0700 Subject: [PATCH 07/16] fix test error info logic --- .../sift_client/_tests/util/test_test_results_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/lib/sift_client/_tests/util/test_test_results_utils.py b/python/lib/sift_client/_tests/util/test_test_results_utils.py index f9722ea21..916e2ecdc 100644 --- a/python/lib/sift_client/_tests/util/test_test_results_utils.py +++ b/python/lib/sift_client/_tests/util/test_test_results_utils.py @@ -126,8 +126,8 @@ def test_measurement_update(self, report_context): def test_error_info(self, report_context, step): test_step = None parent_step_path = step.current_step.step_path - open_step_result = report_context.open_step_results.get(parent_step_path, False) - any_failures = report_context.any_failures + initial_open_step_result = report_context.open_step_results.get(parent_step_path, True) + initial_any_failures = report_context.any_failures with report_context.new_step("Test Error", "Test Error Description") as new_step: test_step = new_step.current_step @@ -137,9 +137,9 @@ def test_error_info(self, report_context, step): assert test_step.error_info.error_message == "Test Error" assert test_step.status == TestStatus.ERROR # If the parent step is not marked as failed already, make sure it remains passed at this point. - if not open_step_result: + if initial_open_step_result: report_context.open_step_results[parent_step_path] = True - if not any_failures: + if not initial_any_failures: report_context.any_failures = False From 66cae8026f0e9e8e2c79ce7debefab36b4445124 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 10:27:15 -0700 Subject: [PATCH 08/16] Format traceback --- python/lib/sift_client/_tests/conftest.py | 2 +- .../_tests/util/test_test_results_utils.py | 2 +- .../lib/sift_client/sift_types/test_report.py | 23 ++++++------ .../sift_client/util/test_results/__init__.py | 35 +++++++++++-------- .../sift_client/util/test_results/bounds.py | 2 +- .../util/test_results/context_manager.py | 31 ++++++++++------ .../sift_client/util/test_results/pytest.py | 15 +++++--- 7 files changed, 67 insertions(+), 43 deletions(-) diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index 78c0f83b2..aa9e78348 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -25,7 +25,7 @@ def sift_client() -> SiftClient: api_key=api_key, grpc_url=grpc_url, rest_url=rest_url, - use_ssl=False, + use_ssl=True, ) ) diff --git a/python/lib/sift_client/_tests/util/test_test_results_utils.py b/python/lib/sift_client/_tests/util/test_test_results_utils.py index 916e2ecdc..d901fc5cc 100644 --- a/python/lib/sift_client/_tests/util/test_test_results_utils.py +++ b/python/lib/sift_client/_tests/util/test_test_results_utils.py @@ -134,7 +134,7 @@ def test_error_info(self, report_context, step): raise Exception("Test Error") assert test_step.error_info is not None assert test_step.error_info.error_code == 1 - assert test_step.error_info.error_message == "Test Error" + assert "Test Error" in test_step.error_info.error_message assert test_step.status == TestStatus.ERROR # If the parent step is not marked as failed already, make sure it remains passed at this point. if initial_open_step_result: diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py index 9f0d3c6c4..74914332b 100644 --- a/python/lib/sift_client/sift_types/test_report.py +++ b/python/lib/sift_client/sift_types/test_report.py @@ -436,9 +436,6 @@ class TestReportBase(ModelCreateUpdateBase): ), } - def _get_proto_class(self) -> type[TestReportProto]: - return TestReportProto - class TestReportUpdate(TestReportBase, ModelUpdate[TestReportProto]): """Update model for TestReport.""" @@ -451,6 +448,9 @@ class TestReportUpdate(TestReportBase, ModelUpdate[TestReportProto]): is_archived: bool | None = None + def _get_proto_class(self) -> type[TestReportProto]: + return TestReportProto + def _add_resource_id_to_proto(self, proto_msg: TestReportProto): if self._resource_id is None: raise ValueError("Resource ID must be set before adding to proto") @@ -466,17 +466,20 @@ class TestReportCreate(TestReportBase, ModelCreate[CreateTestReportRequestProto] start_time: datetime end_time: datetime + def _get_proto_class(self) -> type[CreateTestReportRequestProto]: + return CreateTestReportRequestProto + def to_proto(self) -> CreateTestReportRequestProto: """Convert to protobuf message with custom logic.""" proto = CreateTestReportRequestProto( status=self.status.value, # type: ignore - name=self.name, - test_system_name=self.test_system_name, - test_case=self.test_case, - serial_number=self.serial_number, - part_number=self.part_number, - system_operator=self.system_operator, - run_id=self.run_id, + name=self.name or "", + test_system_name=self.test_system_name or "", + test_case=self.test_case or "", + serial_number=self.serial_number or "", + part_number=self.part_number or "", + system_operator=self.system_operator or "", + run_id=self.run_id if self.run_id else "", metadata=metadata_dict_to_proto(self.metadata) if self.metadata else {}, ) diff --git a/python/lib/sift_client/util/test_results/__init__.py b/python/lib/sift_client/util/test_results/__init__.py index 9734fa1ee..36ff86cf3 100644 --- a/python/lib/sift_client/util/test_results/__init__.py +++ b/python/lib/sift_client/util/test_results/__init__.py @@ -3,27 +3,32 @@ This module provides utilities for working with test results. # Context Managers +- `ReportContext` - Context manager for a new TestReport. - `NewStep` - Context manager to create a new step in a test report. -- `ReportContext` - Context for a new TestReport. Mostly serves as a store to communicate between step context managers since they can be nested or siblings. ### Examples ```python -rc = ReportContext.create(client, name="Example Report", description="Example Report") - -with rc.new_step(name="Setup") as step: - controller_setup(step) -with rc.new_step(name="Example Step", description=desc) as parent_step: - cmd_interface.cmd("ec1", "rtv.cmd", 75.0) - sleep(0.01) - - with rc.new_step(name="Substep 1", description="Measure position") as step: - ec = "ec1" - pos_channel = "rtv.pos" - pos = tlm.read(ec, pos_channel) - success = step.measure(pos, name=f"{ec}.{pos_channel}", bounds=(min=74.9, max=75.1)) - return success # This is optional for other uses, but the step and its parents will be updated correctly i.e. failed if the measurement fails. +with ReportContext(client, name="Example Report", description="Example Report") as rc: + with rc.new_step(name="Setup") as step: + controller_setup(step) + with rc.new_step(name="Example Step", description=desc) as parent_step: + cmd_interface.cmd("ec1", "rtv.cmd", 75.0) + sleep(0.01) + + with parent_step.substep(name="Substep 1", description="Measure position") as substep: + ec = "ec1" + pos_channel = "rtv.pos" + pos = tlm.read(ec, pos_channel) + result = substep.measure(pos, name=f"{ec}.{pos_channel}", bounds=(min=74.9, max=75.1)) + return result # This is optional for other uses, but the step and its parents will be updated correctly i.e. failed if the measurement fails. ``` +#### Pytest Fixtures + +The report context and steps can also be accessed in pytest via the `report_context` and `step` fixtures. +These fixtures are set to autouse and will automatically create a report and steps for each test function. +If you want each module(file) to be marked as a step w/ each test as a substep, import the `module_substep` fixture. + """ from .context_manager import NewStep, ReportContext diff --git a/python/lib/sift_client/util/test_results/bounds.py b/python/lib/sift_client/util/test_results/bounds.py index bb74386c1..d06ac13cf 100644 --- a/python/lib/sift_client/util/test_results/bounds.py +++ b/python/lib/sift_client/util/test_results/bounds.py @@ -66,4 +66,4 @@ def evaluate_measurement_bounds( measurement.passed = ( measurement.passed and measurement.numeric_bounds.max >= float_value ) - return measurement.passed + return bool(measurement.passed) diff --git a/python/lib/sift_client/util/test_results/context_manager.py b/python/lib/sift_client/util/test_results/context_manager.py index 2a3481d02..188239861 100644 --- a/python/lib/sift_client/util/test_results/context_manager.py +++ b/python/lib/sift_client/util/test_results/context_manager.py @@ -3,6 +3,7 @@ import getpass import os import socket +import traceback from contextlib import AbstractContextManager from datetime import datetime, timezone from typing import TYPE_CHECKING @@ -10,6 +11,7 @@ from sift_client.sift_types.test_report import ( ErrorInfo, NumericBounds, + TestMeasurement, TestMeasurementCreate, TestReport, TestReportCreate, @@ -107,7 +109,7 @@ def create_step(self, name: str, description: str | None = None) -> TestStep: step = self.report.client.test_results.create_step( TestStepCreate( - test_report_id=self.report.id_, + test_report_id=str(self.report.id_), name=name, step_type=TestStepType.ACTION, step_path=step_path, @@ -128,6 +130,13 @@ def create_step(self, name: str, description: str | None = None) -> TestStep: return step + def report_measurement(self, measurement: TestMeasurement, step: TestStep): + """Report a failure to the report context.""" + # Failures will be propogated when the step exits. + if not measurement.passed: + self.open_step_results[step.step_path] = False + self.any_failures = True + def resolve_and_propagate_step_result( self, step: TestStep, @@ -200,15 +209,16 @@ def __enter__(self): return self - def __exit__(self, exc, exc_value, traceback): + def __exit__(self, exc, exc_value, tb): error_info = None if exc: + trace = "".join(traceback.format_exception(exc, exc_value, tb, limit=10)) error_info = ErrorInfo( error_code=1, - error_message=str(exc_value), + error_message=trace, ) - print("Captured exception: ", exc_value) - print("error info: ", error_info) + + # Resolve the status of this step (i.e. fail if children failed) and propagate the result to the parent step. result = self.report_context.resolve_and_propagate_step_result( self.current_step, self.parent_step, error_info ) @@ -224,7 +234,8 @@ def __exit__(self, exc, exc_value, traceback): "error_info": error_info, } ) - # Update the last step to the parent. + + # Now that the step is updated. Let the report context handle removing it from the stack and updating the report context. self.report_context.exit_step(self.current_step) return True @@ -240,18 +251,16 @@ def measure( returns: The measurement object. """ + assert self.current_step is not None create = TestMeasurementCreate( - test_step_id=self.current_step.id_, + test_step_id=str(self.current_step.id_), name=name, passed=True, timestamp=datetime.now(timezone.utc), ) evaluate_measurement_bounds(create, value, bounds) measurement = self.client.test_results.create_measurement(create) - - if not create.passed: - # Propogate failures to the report context so the step will be marked correctly when it exists context. - self.report_context.open_step_results[self.current_step.step_path] = False + self.report_context.report_measurement(measurement, self.current_step) return measurement.passed diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index fe9be9aff..72219a774 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Generator import pytest @@ -10,10 +10,13 @@ if TYPE_CHECKING: from sift_client.client import SiftClient + from sift_client.util.test_results.context_manager import NewStep @pytest.fixture(scope="session", autouse=True) -def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> ReportContext: +def report_context( + sift_client: SiftClient, request: pytest.FixtureRequest +) -> Generator[ReportContext, None, None]: """Create a report context for the session.""" test_path = Path(request.config.invocation_params.args[0]) with ReportContext( @@ -25,7 +28,9 @@ def report_context(sift_client: SiftClient, request: pytest.FixtureRequest) -> R @pytest.fixture(autouse=True) -def step(report_context: ReportContext, request: pytest.FixtureRequest): +def step( + report_context: ReportContext, request: pytest.FixtureRequest +) -> Generator[NewStep, None, None]: """Create an outer step for the function.""" name = str(request.node.name) with report_context.new_step(name=name) as new_step: @@ -33,7 +38,9 @@ def step(report_context: ReportContext, request: pytest.FixtureRequest): @pytest.fixture(scope="module", autouse=True) -def module_substep(report_context: ReportContext, request: pytest.FixtureRequest): +def module_substep( + report_context: ReportContext, request: pytest.FixtureRequest +) -> Generator[NewStep, None, None]: """Create a step per module.""" name = str(request.node.name) with report_context.new_step(name=name) as new_step: From 74085804f2060ce6873af169b173053011b37da0 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 11:29:33 -0700 Subject: [PATCH 09/16] add check on sift connection before running report context fixtures --- python/lib/sift_client/_tests/conftest.py | 19 ++++++++- .../_tests/resources/test_test_results.py | 11 +---- python/lib/sift_client/client.py | 2 +- .../sift_client/util/test_results/pytest.py | 42 ++++++++++++------- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/python/lib/sift_client/_tests/conftest.py b/python/lib/sift_client/_tests/conftest.py index aa9e78348..a5ab84d26 100644 --- a/python/lib/sift_client/_tests/conftest.py +++ b/python/lib/sift_client/_tests/conftest.py @@ -20,7 +20,7 @@ def sift_client() -> SiftClient: rest_url = os.getenv("SIFT_REST_URI", "localhost:8080") api_key = os.getenv("SIFT_API_KEY", "") - return SiftClient( + client = SiftClient( connection_config=SiftConnectionConfig( api_key=api_key, grpc_url=grpc_url, @@ -29,6 +29,23 @@ def sift_client() -> SiftClient: ) ) + return client + + +@pytest.fixture(scope="session") +def client_has_connection(sift_client): + """Check if the SiftClient has a connection to the Sift server. + + Can be used to skip tests that require a connection to the Sift server. + """ + has_connection = False + try: + sift_client.ping.ping() + has_connection = True + except Exception: + has_connection = False + return has_connection + @pytest.fixture def mock_client(): diff --git a/python/lib/sift_client/_tests/resources/test_test_results.py b/python/lib/sift_client/_tests/resources/test_test_results.py index ff4993098..a2f0a3a5e 100644 --- a/python/lib/sift_client/_tests/resources/test_test_results.py +++ b/python/lib/sift_client/_tests/resources/test_test_results.py @@ -38,8 +38,7 @@ class TestResultsTest: test_steps: ClassVar[dict[str, TestStep]] = {} test_measurements: ClassVar[dict[str, TestMeasurement]] = {} - def test_create_test_report(self, sift_client, step, nostromo_run): - print("STEP", step) + def test_create_test_report(self, sift_client, nostromo_run): # Create a test report simulated_time = datetime.now(timezone.utc) test_report = sift_client.test_results.create( @@ -56,13 +55,8 @@ def test_create_test_report(self, sift_client, step, nostromo_run): assert test_report.id_ is not None assert test_report.run_id == nostromo_run.id_ self.test_reports["basic_test_report"] = test_report - step.measure(name="test_report_created", value=True) - with step.substep(name="nested step") as nested_step: - nested_step.measure(name="nested_step_created", value=True) - with nested_step.substep(name="nested nested step") as nested_nested_step: - nested_nested_step.measure(name="nested_nested_step_created", value=True) - def test_create_test_steps(self, sift_client, step): + def test_create_test_steps(self, sift_client): test_report = self.test_reports.get("basic_test_report") if not test_report: pytest.skip("Need to create a test report first") @@ -82,7 +76,6 @@ def test_create_test_steps(self, sift_client, step): ), ) simulated_time = simulated_time + timedelta(seconds=10.1) - step.measure(name="step1_created", value=True) # Create a step using a dict step1_1 = sift_client.test_results.create_step( { diff --git a/python/lib/sift_client/client.py b/python/lib/sift_client/client.py index 2a2252ef8..52082e024 100644 --- a/python/lib/sift_client/client.py +++ b/python/lib/sift_client/client.py @@ -97,7 +97,7 @@ class SiftClient( tags: TagsAPI """Instance of the Tags API for making synchronous requests.""" - test_results: TestResultsAPI + test_results: TestResultsAPI = None """Instance of the Test Results API for making synchronous requests.""" async_: AsyncAPIs diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 72219a774..56471f32f 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -15,33 +15,43 @@ @pytest.fixture(scope="session", autouse=True) def report_context( - sift_client: SiftClient, request: pytest.FixtureRequest + sift_client: SiftClient, client_has_connection: bool, request: pytest.FixtureRequest ) -> Generator[ReportContext, None, None]: """Create a report context for the session.""" - test_path = Path(request.config.invocation_params.args[0]) - with ReportContext( - sift_client, - name=f"{test_path.name} {datetime.now(timezone.utc).isoformat()}", - test_case=str(test_path), - ) as context: - yield context + if client_has_connection: + print("Creating report context", sift_client.test_results) + test_path = Path(request.config.invocation_params.args[0]) + with ReportContext( + sift_client, + name=f"{test_path.name} {datetime.now(timezone.utc).isoformat()}", + test_case=str(test_path), + ) as context: + yield context + else: + yield @pytest.fixture(autouse=True) def step( - report_context: ReportContext, request: pytest.FixtureRequest + report_context: ReportContext, client_has_connection: bool, request: pytest.FixtureRequest ) -> Generator[NewStep, None, None]: """Create an outer step for the function.""" - name = str(request.node.name) - with report_context.new_step(name=name) as new_step: - yield new_step + if client_has_connection: + name = str(request.node.name) + with report_context.new_step(name=name) as new_step: + yield new_step + else: + yield @pytest.fixture(scope="module", autouse=True) def module_substep( - report_context: ReportContext, request: pytest.FixtureRequest + report_context: ReportContext, client_has_connection: bool, request: pytest.FixtureRequest ) -> Generator[NewStep, None, None]: """Create a step per module.""" - name = str(request.node.name) - with report_context.new_step(name=name) as new_step: - yield new_step + if client_has_connection: + name = str(request.node.name) + with report_context.new_step(name=name) as new_step: + yield new_step + else: + yield From 0eca2836357c036e7b590c3f75bffb5d038a2c6f Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 11:59:00 -0700 Subject: [PATCH 10/16] mypy --- python/lib/sift_client/client.py | 2 +- python/lib/sift_client/util/test_results/pytest.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/python/lib/sift_client/client.py b/python/lib/sift_client/client.py index 52082e024..2a2252ef8 100644 --- a/python/lib/sift_client/client.py +++ b/python/lib/sift_client/client.py @@ -97,7 +97,7 @@ class SiftClient( tags: TagsAPI """Instance of the Tags API for making synchronous requests.""" - test_results: TestResultsAPI = None + test_results: TestResultsAPI """Instance of the Test Results API for making synchronous requests.""" async_: AsyncAPIs diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 56471f32f..d8214c59b 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -16,10 +16,9 @@ @pytest.fixture(scope="session", autouse=True) def report_context( sift_client: SiftClient, client_has_connection: bool, request: pytest.FixtureRequest -) -> Generator[ReportContext, None, None]: +) -> Generator[ReportContext | None, None, None]: """Create a report context for the session.""" if client_has_connection: - print("Creating report context", sift_client.test_results) test_path = Path(request.config.invocation_params.args[0]) with ReportContext( sift_client, @@ -28,30 +27,30 @@ def report_context( ) as context: yield context else: - yield + yield None @pytest.fixture(autouse=True) def step( report_context: ReportContext, client_has_connection: bool, request: pytest.FixtureRequest -) -> Generator[NewStep, None, None]: +) -> Generator[NewStep | None, None, None]: """Create an outer step for the function.""" if client_has_connection: name = str(request.node.name) with report_context.new_step(name=name) as new_step: yield new_step else: - yield + yield None @pytest.fixture(scope="module", autouse=True) def module_substep( report_context: ReportContext, client_has_connection: bool, request: pytest.FixtureRequest -) -> Generator[NewStep, None, None]: +) -> Generator[NewStep | None, None, None]: """Create a step per module.""" if client_has_connection: name = str(request.node.name) with report_context.new_step(name=name) as new_step: yield new_step else: - yield + yield None From 3ab9e00ad615ffbdcdb22f064270141a0a317973 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 12:08:48 -0700 Subject: [PATCH 11/16] update protos for other clients --- .../sift/test_reports/v1/test_reports.pb.go | 1189 +++++++++-------- .../v1/test_reports_vtproto.pb.go | 180 +++ .../sift_rs/src/gen/sift.test_reports.v1.rs | 4 + .../src/gen/sift.test_reports.v1.serde.rs | 36 + .../src/gen/sift.test_reports.v1.tonic.rs | 4 + 5 files changed, 829 insertions(+), 584 deletions(-) diff --git a/go/gen/sift/test_reports/v1/test_reports.pb.go b/go/gen/sift/test_reports/v1/test_reports.pb.go index 40996d2a8..3bd06ed48 100644 --- a/go/gen/sift/test_reports/v1/test_reports.pb.go +++ b/go/gen/sift/test_reports/v1/test_reports.pb.go @@ -231,6 +231,8 @@ type TestReport struct { ArchivedDate *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=archived_date,json=archivedDate,proto3" json:"archived_date,omitempty"` // Whether the test run is archived (externally exposed) IsArchived bool `protobuf:"varint,13,opt,name=is_archived,json=isArchived,proto3" json:"is_archived,omitempty"` + // The run ID for the test run + RunId string `protobuf:"bytes,14,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` } func (x *TestReport) Reset() { @@ -356,6 +358,13 @@ func (x *TestReport) GetIsArchived() bool { return false } +func (x *TestReport) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + type TestStep struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -976,6 +985,8 @@ type CreateTestReportRequest struct { PartNumber string `protobuf:"bytes,9,opt,name=part_number,json=partNumber,proto3" json:"part_number,omitempty"` // Unique identifier for user owner SystemOperator string `protobuf:"bytes,10,opt,name=system_operator,json=systemOperator,proto3" json:"system_operator,omitempty"` + // The run ID for the test report + RunId string `protobuf:"bytes,11,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` } func (x *CreateTestReportRequest) Reset() { @@ -1080,6 +1091,13 @@ func (x *CreateTestReportRequest) GetSystemOperator() string { return "" } +func (x *CreateTestReportRequest) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + // Response message for CreateTestReport type CreateTestReportResponse struct { state protoimpl.MessageState @@ -1246,7 +1264,7 @@ type ListTestReportsRequest struct { // A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. // Available fields to filter by are `test_report_id`, `status`, `name`, `test_system_name`, // `test_case`, `start_time`, `end_time`, `serial_number`, `created_by_user_id`, `modified_by_user_id`, - // `part_number`, `system_operator`, `archived_date`, and `metadata`. + // `part_number`, `system_operator`, `run_id`, `archived_date`, and `metadata`. // Metadata can be used in filters by using `metadata.{metadata_key_name}` as the field name. // For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). // For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testreport). Optional. @@ -1388,7 +1406,7 @@ type UpdateTestReportRequest struct { TestReport *TestReport `protobuf:"bytes,1,opt,name=test_report,json=testReport,proto3" json:"test_report,omitempty"` // The field mask specifying which fields to update. The fields available to be updated are // `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, - // `part_number`, `system_operator`, and `is_archived`. + // `part_number`, `system_operator`, `run_id`, and `is_archived`. UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` } @@ -2799,7 +2817,7 @@ var file_sift_test_reports_v1_test_reports_proto_rawDesc = []byte{ 0x6f, 0x1a, 0x1f, 0x73, 0x69, 0x66, 0x74, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x73, 0x69, 0x66, 0x74, 0x2f, 0x75, 0x6e, 0x69, 0x74, 0x2f, 0x76, 0x32, - 0x2f, 0x75, 0x6e, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x88, 0x05, 0x0a, 0x0a, + 0x2f, 0x75, 0x6e, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa4, 0x05, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, @@ -2840,159 +2858,285 @@ var file_sift_test_reports_v1_test_reports_proto_rawDesc = []byte{ 0x41, 0x01, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x69, 0x73, 0x41, 0x72, - 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x22, 0xaf, 0x04, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x65, 0x70, 0x12, 0x25, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, + 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x05, 0x72, 0x75, 0x6e, + 0x49, 0x64, 0x22, 0xaf, 0x04, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, + 0x25, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, + 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, + 0x64, 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0c, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x09, + 0x73, 0x74, 0x65, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x22, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x54, + 0x79, 0x70, 0x65, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x73, 0x74, 0x65, 0x70, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x73, 0x74, 0x65, 0x70, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x3e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x43, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x59, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x22, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0xab, 0x05, 0x0a, 0x0f, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x0e, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, + 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x59, 0x0a, 0x10, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0f, 0x6d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, - 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, - 0x73, 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, - 0x41, 0x01, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, - 0xe0, 0x41, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x44, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x65, 0x70, 0x54, 0x79, 0x70, 0x65, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x73, 0x74, - 0x65, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, - 0x73, 0x74, 0x65, 0x70, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, - 0x69, 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x59, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0xab, 0x05, 0x0a, 0x0f, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, - 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x0e, 0x6d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x49, 0x64, 0x12, 0x59, 0x0a, 0x10, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, - 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0f, 0x6d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, - 0x02, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x73, 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x29, - 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x6e, 0x75, 0x6d, - 0x65, 0x72, 0x69, 0x63, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, - 0x48, 0x00, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c, - 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2b, 0x0a, 0x04, - 0x75, 0x6e, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x66, - 0x74, 0x2e, 0x75, 0x6e, 0x69, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x42, 0x03, - 0xe0, 0x41, 0x01, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, - 0x65, 0x72, 0x69, 0x63, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, - 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x48, 0x01, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, - 0x63, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x75, 0x6e, - 0x64, 0x73, 0x48, 0x01, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x75, 0x6e, - 0x64, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x08, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x12, - 0x3d, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x07, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x73, 0x22, 0x57, 0x0a, 0x0d, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x42, 0x6f, 0x75, 0x6e, - 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x42, - 0x03, 0xe0, 0x41, 0x01, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1a, - 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x01, - 0x48, 0x01, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, - 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x61, 0x78, 0x22, 0x3a, 0x0a, 0x0c, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2a, 0x0a, 0x0e, 0x65, 0x78, - 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x44, 0x0a, 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x18, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0c, + 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, + 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x6f, 0x6f, 0x6c, + 0x65, 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x75, 0x6e, + 0x69, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, + 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0xfe, 0x03, 0x0a, 0x17, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x65, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x2d, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0e, - 0x74, 0x65, 0x73, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, - 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, - 0x12, 0x3e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x42, 0x6f, 0x75, 0x6e, + 0x64, 0x73, 0x48, 0x01, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x42, 0x6f, 0x75, + 0x6e, 0x64, 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x69, 0x66, + 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x48, 0x01, + 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1b, + 0x0a, 0x06, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x42, 0x03, + 0xe0, 0x41, 0x02, 0x52, 0x06, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0x57, 0x0a, + 0x0d, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1a, + 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x01, + 0x48, 0x00, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x03, 0x6d, 0x61, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x48, 0x01, 0x52, 0x03, + 0x6d, 0x61, 0x78, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x69, 0x6e, 0x42, 0x06, + 0x0a, 0x04, 0x5f, 0x6d, 0x61, 0x78, 0x22, 0x3a, 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2a, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, + 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x44, 0x0a, 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, + 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x18, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0a, 0x74, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x9a, 0x04, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x10, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0e, 0x74, 0x65, 0x73, 0x74, + 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x63, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, + 0x41, 0x02, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x08, + 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, + 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x69, 0x66, + 0x74, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x03, 0xe0, 0x41, 0x01, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x0d, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, + 0x70, 0x61, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x05, 0x72, + 0x75, 0x6e, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x22, 0x41, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x12, 0x1e, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, + 0x22, 0x86, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa3, 0x01, 0x0a, 0x17, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, + 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, - 0x03, 0xe0, 0x41, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x28, - 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, - 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, - 0x41, 0x01, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, - 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0e, 0x73, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x5d, 0x0a, 0x18, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, - 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x41, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x5a, - 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, + 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, + 0x5d, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x44, + 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x49, 0x64, 0x22, 0x1a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x59, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, + 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x09, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0a, - 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x16, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x22, 0x55, 0x0a, 0x16, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, + 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, + 0x65, 0x70, 0x22, 0x99, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, + 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, + 0xe0, 0x41, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, + 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, + 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x22, 0x7e, + 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, + 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x09, 0x74, 0x65, 0x73, + 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9b, + 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, + 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, + 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x01, + 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x55, 0x0a, 0x16, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, + 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, + 0x74, 0x65, 0x70, 0x22, 0x3e, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0c, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, + 0x70, 0x49, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x75, 0x0a, + 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, + 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x03, + 0xe0, 0x41, 0x02, 0x52, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x71, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x78, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, + 0x10, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x22, 0x91, 0x01, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x1a, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x18, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x0f, 0x6d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0e, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, @@ -3001,461 +3145,338 @@ var file_sift_test_reports_v1_test_reports_proto_rawDesc = []byte{ 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, - 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x22, 0x86, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, - 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, + 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x22, 0x9a, 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, + 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x11, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x74, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, + 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x34, 0x0a, 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, + 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, + 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x16, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3b, 0x0a, 0x1c, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, + 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x35, 0x0a, 0x1d, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0xb7, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x74, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, - 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xa3, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x46, 0x0a, - 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, - 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x5d, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0a, 0x74, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x44, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, - 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x1a, 0x0a, 0x18, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x40, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x71, 0x0a, 0x1d, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x10, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, - 0x53, 0x74, 0x65, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x65, 0x70, 0x22, 0x55, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, - 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, - 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, - 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x22, 0x99, 0x01, 0x0a, 0x14, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, - 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, - 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x07, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x22, 0x7e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, - 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3d, 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x65, 0x70, 0x52, 0x09, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x12, 0x26, - 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9b, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x40, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x65, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, - 0x65, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, - 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, - 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x55, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, - 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, - 0x70, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x22, 0x3e, 0x0a, 0x15, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, - 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x0a, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x49, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x75, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0f, 0x74, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x71, 0x0a, 0x1d, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, - 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0f, - 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, - 0x78, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x57, 0x0a, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, - 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x10, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x1e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x1a, - 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x18, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x2c, 0x0a, 0x0f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0e, 0x6d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0xa0, 0x01, - 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x12, 0x1e, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, - 0x22, 0x9a, 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x52, 0x0a, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x10, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x34, 0x0a, - 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x22, 0x2e, 0x0a, 0x16, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, - 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x22, 0x3b, 0x0a, 0x1c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x22, 0x35, 0x0a, 0x1d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xb7, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0f, 0x74, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x4a, 0x0a, 0x1c, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x0e, + 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0f, - 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x40, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, - 0x42, 0x03, 0xe0, 0x41, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, - 0x6b, 0x22, 0x71, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x50, 0x0a, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x4a, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x0e, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, - 0x02, 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, - 0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2a, 0xd6, 0x01, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, - 0x11, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x52, 0x41, - 0x46, 0x54, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x45, 0x44, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, - 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, - 0x11, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, - 0x06, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x53, 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, 0x10, 0x07, 0x2a, 0xa1, 0x01, 0x0a, 0x0c, 0x54, - 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x54, - 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x54, - 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, - 0x51, 0x55, 0x45, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x45, 0x53, 0x54, - 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, - 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1f, 0x0a, - 0x1b, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x4f, 0x4c, 0x10, 0x04, 0x2a, 0xc4, - 0x01, 0x0a, 0x13, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x21, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, - 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, - 0x1c, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, - 0x20, 0x0a, 0x1c, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, - 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, - 0x03, 0x12, 0x21, 0x0a, 0x1d, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, - 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x45, - 0x41, 0x4e, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, - 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x49, - 0x4d, 0x49, 0x54, 0x10, 0x05, 0x32, 0xd7, 0x1c, 0x0a, 0x11, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xe4, 0x01, 0x0a, 0x10, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xd6, 0x01, 0x0a, 0x0a, 0x54, 0x65, + 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x45, 0x53, 0x54, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x52, 0x41, 0x46, 0x54, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, + 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x53, 0x53, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, + 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, + 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, + 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x5f, 0x50, + 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x06, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, + 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, + 0x10, 0x07, 0x2a, 0xa1, 0x01, 0x0a, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x51, 0x55, 0x45, 0x4e, 0x43, 0x45, 0x10, 0x01, + 0x12, 0x18, 0x0a, 0x14, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x45, + 0x53, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x43, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x54, + 0x45, 0x50, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x4e, + 0x54, 0x52, 0x4f, 0x4c, 0x10, 0x04, 0x2a, 0xc4, 0x01, 0x0a, 0x13, 0x54, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, + 0x0a, 0x21, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, + 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, + 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, + 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x45, 0x53, 0x54, 0x5f, + 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, 0x54, 0x45, 0x53, + 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, + 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x05, 0x32, 0xd7, 0x1c, + 0x0a, 0x11, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0xe4, 0x01, 0x0a, 0x10, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x71, 0x92, 0x41, 0x48, 0x12, 0x10, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x34, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, - 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x66, 0x72, - 0x6f, 0x6d, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x2d, 0x75, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, - 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3a, 0x69, 0x6d, 0x70, 0x6f, - 0x72, 0x74, 0x12, 0xbe, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, - 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x29, 0x12, 0x10, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x71, 0x92, 0x41, 0x48, 0x12, 0x10, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x34, + 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x6c, + 0x72, 0x65, 0x61, 0x64, 0x79, 0x2d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x12, 0xc4, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2a, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, - 0x92, 0x41, 0x2a, 0x12, 0x0d, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x1a, 0x19, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, - 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, - 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xcc, 0x01, 0x0a, 0x0f, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x2c, - 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5c, 0x92, 0x41, 0x3d, - 0x12, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x1a, 0x2a, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, - 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0xbe, 0x01, 0x0a, 0x10, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, - 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, - 0x41, 0x29, 0x12, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, - 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x19, 0x3a, 0x01, 0x2a, 0x32, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, - 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, + 0x72, 0x74, 0x73, 0x3a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0xbe, 0x01, 0x0a, 0x10, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, - 0x92, 0x41, 0x29, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, + 0x92, 0x41, 0x29, 0x12, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x27, 0x2a, 0x25, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, - 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xb2, 0x01, 0x0a, 0x0e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, 0x2b, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, - 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0xc4, 0x01, 0x0a, 0x0d, + 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2a, 0x2e, + 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x92, 0x41, 0x25, 0x12, 0x0e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x1a, 0x13, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, - 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0xc0, - 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, - 0x12, 0x2a, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, - 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x92, 0x41, 0x39, 0x12, 0x0d, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x28, 0x4c, - 0x69, 0x73, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x73, 0x20, - 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, - 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x2a, 0x12, 0x0d, 0x47, 0x65, 0x74, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x19, 0x47, 0x65, 0x74, 0x73, + 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x12, 0xcc, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x5c, 0x92, 0x41, 0x3d, 0x12, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x1a, 0x2a, 0x4c, 0x69, 0x73, 0x74, 0x73, + 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x12, 0xbe, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x92, 0x41, 0x29, 0x12, 0x10, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x32, 0x14, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x92, 0x41, 0x29, 0x12, 0x10, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x15, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x2a, 0x25, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, + 0x7d, 0x12, 0xb2, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x45, 0x92, 0x41, 0x25, 0x12, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, - 0x53, 0x74, 0x65, 0x70, 0x1a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x45, 0x92, 0x41, 0x25, 0x12, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x1a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, - 0x01, 0x2a, 0x32, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, - 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0xbe, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, + 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0xc0, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, + 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x12, 0x2a, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x56, 0x92, 0x41, 0x39, 0x12, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x28, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x0e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x12, 0x2b, 0x2e, 0x73, + 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, + 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, - 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41, 0x25, 0x12, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x1a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x23, 0x2a, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, - 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, - 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xdc, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x33, 0x12, - 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, - 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x82, 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x92, 0x41, 0x25, 0x12, 0x0e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x1a, 0x13, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, + 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x32, 0x12, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0xbe, + 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, + 0x70, 0x12, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41, + 0x25, 0x12, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, + 0x70, 0x1a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x2a, 0x21, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, + 0x2f, 0x7b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x5f, 0x69, 0x64, 0x7d, 0x12, + 0xdc, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, + 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x33, 0x12, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, + 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, + 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x82, + 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x73, 0x69, 0x66, 0x74, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, + 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7d, 0x92, 0x41, 0x50, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x33, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, - 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7d, 0x92, 0x41, - 0x50, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x36, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x73, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, - 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, - 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x12, 0xea, 0x01, 0x0a, 0x14, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x31, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6b, 0x92, 0x41, 0x47, - 0x12, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x2f, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x74, 0x65, - 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, - 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, - 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0xcb, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x12, 0x2b, 0x2e, 0x73, 0x69, - 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, + 0x73, 0x1a, 0x36, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x70, 0x6c, 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, + 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, + 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x12, 0xea, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x31, 0x2e, 0x73, + 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x6b, 0x92, 0x41, 0x47, 0x12, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x2f, + 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0xcb, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, + 0x65, 0x70, 0x73, 0x12, 0x2b, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2c, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, + 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, + 0x92, 0x41, 0x3b, 0x12, 0x0e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, + 0x65, 0x70, 0x73, 0x1a, 0x29, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, + 0x20, 0x73, 0x74, 0x65, 0x70, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, + 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xf5, + 0x01, 0x0a, 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x92, 0x41, 0x3b, 0x12, 0x0e, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x29, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x73, 0x20, 0x77, - 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x65, 0x70, 0x73, - 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xf5, 0x01, 0x0a, 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, + 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x73, 0x92, 0x41, 0x49, 0x12, 0x15, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x30, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, + 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xdc, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x92, 0x41, 0x49, 0x12, 0x15, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x30, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x65, - 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, - 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, - 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xdc, - 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x33, 0x12, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, - 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x5a, 0x92, 0x41, 0x33, 0x12, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, - 0x01, 0x2a, 0x32, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, - 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0xea, 0x01, - 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, - 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x32, 0x19, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0xea, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x32, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x68, 0x92, 0x41, 0x33, 0x12, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x73, 0x69, - 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x68, 0x92, 0x41, 0x33, 0x12, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x2a, 0x2a, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x1a, 0x23, 0x92, 0x41, 0x20, 0x12, - 0x1e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x42, - 0xfc, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x66, - 0x74, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x69, 0x66, 0x74, 0x2f, 0x67, 0x6f, 0x2f, - 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x69, 0x66, 0x74, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x54, 0x58, 0xaa, 0x02, 0x13, - 0x53, 0x69, 0x66, 0x74, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x2e, 0x56, 0x31, 0xca, 0x02, 0x13, 0x53, 0x69, 0x66, 0x74, 0x5c, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1f, 0x53, 0x69, 0x66, 0x74, - 0x5c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5c, 0x56, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x53, 0x69, - 0x66, 0x74, 0x3a, 0x3a, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3a, - 0x3a, 0x56, 0x31, 0x92, 0x41, 0x18, 0x12, 0x16, 0x0a, 0x14, 0x54, 0x65, 0x73, 0x74, 0x20, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x74, 0x65, 0x73, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x2a, 0x2a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x1a, 0x23, 0x92, 0x41, 0x20, 0x12, 0x1e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x42, 0xfc, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, + 0x73, 0x69, 0x66, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x66, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, + 0x73, 0x69, 0x66, 0x74, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x69, 0x66, 0x74, + 0x2f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x76, 0x31, + 0x3b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x76, 0x31, 0xa2, + 0x02, 0x03, 0x53, 0x54, 0x58, 0xaa, 0x02, 0x13, 0x53, 0x69, 0x66, 0x74, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x13, 0x53, 0x69, + 0x66, 0x74, 0x5c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5c, 0x56, + 0x31, 0xe2, 0x02, 0x1f, 0x53, 0x69, 0x66, 0x74, 0x5c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x53, 0x69, 0x66, 0x74, 0x3a, 0x3a, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x92, 0x41, 0x18, 0x12, 0x16, + 0x0a, 0x14, 0x54, 0x65, 0x73, 0x74, 0x20, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/gen/sift/test_reports/v1/test_reports_vtproto.pb.go b/go/gen/sift/test_reports/v1/test_reports_vtproto.pb.go index d23f6e295..b41d487de 100644 --- a/go/gen/sift/test_reports/v1/test_reports_vtproto.pb.go +++ b/go/gen/sift/test_reports/v1/test_reports_vtproto.pb.go @@ -49,6 +49,7 @@ func (m *TestReport) CloneVT() *TestReport { r.SystemOperator = m.SystemOperator r.ArchivedDate = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.ArchivedDate).CloneVT()) r.IsArchived = m.IsArchived + r.RunId = m.RunId if rhs := m.Metadata; rhs != nil { tmpContainer := make([]*v1.MetadataValue, len(rhs)) for k, v := range rhs { @@ -290,6 +291,7 @@ func (m *CreateTestReportRequest) CloneVT() *CreateTestReportRequest { r.SerialNumber = m.SerialNumber r.PartNumber = m.PartNumber r.SystemOperator = m.SystemOperator + r.RunId = m.RunId if rhs := m.Metadata; rhs != nil { tmpContainer := make([]*v1.MetadataValue, len(rhs)) for k, v := range rhs { @@ -943,6 +945,9 @@ func (this *TestReport) EqualVT(that *TestReport) bool { if this.IsArchived != that.IsArchived { return false } + if this.RunId != that.RunId { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1326,6 +1331,9 @@ func (this *CreateTestReportRequest) EqualVT(that *CreateTestReportRequest) bool if this.SystemOperator != that.SystemOperator { return false } + if this.RunId != that.RunId { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -2758,6 +2766,13 @@ func (m *TestReport) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.RunId) > 0 { + i -= len(m.RunId) + copy(dAtA[i:], m.RunId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RunId))) + i-- + dAtA[i] = 0x72 + } if m.IsArchived { i-- if m.IsArchived { @@ -3445,6 +3460,13 @@ func (m *CreateTestReportRequest) MarshalToSizedBufferVT(dAtA []byte) (int, erro i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.RunId) > 0 { + i -= len(m.RunId) + copy(dAtA[i:], m.RunId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RunId))) + i-- + dAtA[i] = 0x5a + } if len(m.SystemOperator) > 0 { i -= len(m.SystemOperator) copy(dAtA[i:], m.SystemOperator) @@ -4955,6 +4977,13 @@ func (m *TestReport) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.RunId) > 0 { + i -= len(m.RunId) + copy(dAtA[i:], m.RunId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RunId))) + i-- + dAtA[i] = 0x72 + } if m.IsArchived { i-- if m.IsArchived { @@ -5659,6 +5688,13 @@ func (m *CreateTestReportRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.RunId) > 0 { + i -= len(m.RunId) + copy(dAtA[i:], m.RunId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RunId))) + i-- + dAtA[i] = 0x5a + } if len(m.SystemOperator) > 0 { i -= len(m.SystemOperator) copy(dAtA[i:], m.SystemOperator) @@ -7203,6 +7239,10 @@ func (m *TestReport) SizeVT() (n int) { if m.IsArchived { n += 2 } + l = len(m.RunId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -7491,6 +7531,10 @@ func (m *CreateTestReportRequest) SizeVT() (n int) { if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + l = len(m.RunId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -8427,6 +8471,38 @@ func (m *TestReport) UnmarshalVT(dAtA []byte) error { } } m.IsArchived = bool(v != 0) + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RunId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RunId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -10066,6 +10142,38 @@ func (m *CreateTestReportRequest) UnmarshalVT(dAtA []byte) error { } m.SystemOperator = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RunId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RunId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -13537,6 +13645,42 @@ func (m *TestReport) UnmarshalVTUnsafe(dAtA []byte) error { } } m.IsArchived = bool(v != 0) + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RunId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.RunId = stringValue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -15256,6 +15400,42 @@ func (m *CreateTestReportRequest) UnmarshalVTUnsafe(dAtA []byte) error { } m.SystemOperator = stringValue iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RunId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.RunId = stringValue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.rs b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.rs index bb9d700d7..c91f26392 100644 --- a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.rs +++ b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.rs @@ -29,6 +29,8 @@ pub struct TestReport { pub archived_date: ::core::option::Option<::pbjson_types::Timestamp>, #[prost(bool, tag="13")] pub is_archived: bool, + #[prost(string, tag="14")] + pub run_id: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -158,6 +160,8 @@ pub struct CreateTestReportRequest { pub part_number: ::prost::alloc::string::String, #[prost(string, tag="10")] pub system_operator: ::prost::alloc::string::String, + #[prost(string, tag="11")] + pub run_id: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.serde.rs b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.serde.rs index 0980d17ff..6eab083bc 100644 --- a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.serde.rs +++ b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.serde.rs @@ -795,6 +795,9 @@ impl serde::Serialize for CreateTestReportRequest { if !self.system_operator.is_empty() { len += 1; } + if !self.run_id.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("sift.test_reports.v1.CreateTestReportRequest", len)?; if self.status != 0 { let v = TestStatus::try_from(self.status) @@ -828,6 +831,9 @@ impl serde::Serialize for CreateTestReportRequest { if !self.system_operator.is_empty() { struct_ser.serialize_field("systemOperator", &self.system_operator)?; } + if !self.run_id.is_empty() { + struct_ser.serialize_field("runId", &self.run_id)?; + } struct_ser.end() } } @@ -855,6 +861,8 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { "partNumber", "system_operator", "systemOperator", + "run_id", + "runId", ]; #[allow(clippy::enum_variant_names)] @@ -869,6 +877,7 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { SerialNumber, PartNumber, SystemOperator, + RunId, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -900,6 +909,7 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { "serialNumber" | "serial_number" => Ok(GeneratedField::SerialNumber), "partNumber" | "part_number" => Ok(GeneratedField::PartNumber), "systemOperator" | "system_operator" => Ok(GeneratedField::SystemOperator), + "runId" | "run_id" => Ok(GeneratedField::RunId), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -929,6 +939,7 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { let mut serial_number__ = None; let mut part_number__ = None; let mut system_operator__ = None; + let mut run_id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Status => { @@ -991,6 +1002,12 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { } system_operator__ = Some(map_.next_value()?); } + GeneratedField::RunId => { + if run_id__.is_some() { + return Err(serde::de::Error::duplicate_field("runId")); + } + run_id__ = Some(map_.next_value()?); + } } } Ok(CreateTestReportRequest { @@ -1004,6 +1021,7 @@ impl<'de> serde::Deserialize<'de> for CreateTestReportRequest { serial_number: serial_number__.unwrap_or_default(), part_number: part_number__.unwrap_or_default(), system_operator: system_operator__.unwrap_or_default(), + run_id: run_id__.unwrap_or_default(), }) } } @@ -3658,6 +3676,9 @@ impl serde::Serialize for TestReport { if self.is_archived { len += 1; } + if !self.run_id.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("sift.test_reports.v1.TestReport", len)?; if !self.test_report_id.is_empty() { struct_ser.serialize_field("testReportId", &self.test_report_id)?; @@ -3700,6 +3721,9 @@ impl serde::Serialize for TestReport { if self.is_archived { struct_ser.serialize_field("isArchived", &self.is_archived)?; } + if !self.run_id.is_empty() { + struct_ser.serialize_field("runId", &self.run_id)?; + } struct_ser.end() } } @@ -3733,6 +3757,8 @@ impl<'de> serde::Deserialize<'de> for TestReport { "archivedDate", "is_archived", "isArchived", + "run_id", + "runId", ]; #[allow(clippy::enum_variant_names)] @@ -3750,6 +3776,7 @@ impl<'de> serde::Deserialize<'de> for TestReport { SystemOperator, ArchivedDate, IsArchived, + RunId, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -3784,6 +3811,7 @@ impl<'de> serde::Deserialize<'de> for TestReport { "systemOperator" | "system_operator" => Ok(GeneratedField::SystemOperator), "archivedDate" | "archived_date" => Ok(GeneratedField::ArchivedDate), "isArchived" | "is_archived" => Ok(GeneratedField::IsArchived), + "runId" | "run_id" => Ok(GeneratedField::RunId), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -3816,6 +3844,7 @@ impl<'de> serde::Deserialize<'de> for TestReport { let mut system_operator__ = None; let mut archived_date__ = None; let mut is_archived__ = None; + let mut run_id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::TestReportId => { @@ -3896,6 +3925,12 @@ impl<'de> serde::Deserialize<'de> for TestReport { } is_archived__ = Some(map_.next_value()?); } + GeneratedField::RunId => { + if run_id__.is_some() { + return Err(serde::de::Error::duplicate_field("runId")); + } + run_id__ = Some(map_.next_value()?); + } } } Ok(TestReport { @@ -3912,6 +3947,7 @@ impl<'de> serde::Deserialize<'de> for TestReport { system_operator: system_operator__.unwrap_or_default(), archived_date: archived_date__, is_archived: is_archived__.unwrap_or_default(), + run_id: run_id__.unwrap_or_default(), }) } } diff --git a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.tonic.rs b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.tonic.rs index 6a9028f3a..526e0e8ef 100644 --- a/rust/crates/sift_rs/src/gen/sift.test_reports.v1.tonic.rs +++ b/rust/crates/sift_rs/src/gen/sift.test_reports.v1.tonic.rs @@ -435,6 +435,8 @@ pub mod test_report_service_client { ); self.inner.unary(req, path, codec).await } + /** Creates multiple test measurements in a single request +*/ pub async fn create_test_measurements( &mut self, request: impl tonic::IntoRequest, @@ -721,6 +723,8 @@ pub mod test_report_service_server { tonic::Response, tonic::Status, >; + /** Creates multiple test measurements in a single request +*/ async fn create_test_measurements( &self, request: tonic::Request, From 2b52739f620775054d13598e690f6c4023c838e1 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 12:13:16 -0700 Subject: [PATCH 12/16] change auto gen'ed case and name --- python/lib/sift_client/util/test_results/pytest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index d8214c59b..b2fdc8aa9 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -20,10 +20,12 @@ def report_context( """Create a report context for the session.""" if client_has_connection: test_path = Path(request.config.invocation_params.args[0]) + base_name = test_path.name if test_path.exists() else " ".join(request.config.invocation_params.args) + test_case = test_path if test_path.exists() else base_name with ReportContext( sift_client, - name=f"{test_path.name} {datetime.now(timezone.utc).isoformat()}", - test_case=str(test_path), + name=f"{base_name} {datetime.now(timezone.utc).isoformat()}", + test_case=str(test_case), ) as context: yield context else: From 0f7313faafa0f2e38a171393afb2354debec8707 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 12:16:27 -0700 Subject: [PATCH 13/16] lint --- python/lib/sift_client/util/test_results/pytest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index b2fdc8aa9..1cf3d7001 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -20,7 +20,11 @@ def report_context( """Create a report context for the session.""" if client_has_connection: test_path = Path(request.config.invocation_params.args[0]) - base_name = test_path.name if test_path.exists() else " ".join(request.config.invocation_params.args) + base_name = ( + test_path.name + if test_path.exists() + else " ".join(request.config.invocation_params.args) + ) test_case = test_path if test_path.exists() else base_name with ReportContext( sift_client, From 2059daa552dd8ca920d867afd356943c1dce853d Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 12:26:44 -0700 Subject: [PATCH 14/16] pytest prefix --- python/lib/sift_client/util/test_results/pytest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/lib/sift_client/util/test_results/pytest.py b/python/lib/sift_client/util/test_results/pytest.py index 1cf3d7001..3d179b379 100644 --- a/python/lib/sift_client/util/test_results/pytest.py +++ b/python/lib/sift_client/util/test_results/pytest.py @@ -23,7 +23,7 @@ def report_context( base_name = ( test_path.name if test_path.exists() - else " ".join(request.config.invocation_params.args) + else "pytest " + " ".join(request.config.invocation_params.args) ) test_case = test_path if test_path.exists() else base_name with ReportContext( From ebc5e80c506b6505f3971871b20eb2799c978bc6 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Fri, 24 Oct 2025 18:15:07 -0700 Subject: [PATCH 15/16] guard return --- .../sift_client/util/test_results/bounds.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/python/lib/sift_client/util/test_results/bounds.py b/python/lib/sift_client/util/test_results/bounds.py index d06ac13cf..b69f7ac24 100644 --- a/python/lib/sift_client/util/test_results/bounds.py +++ b/python/lib/sift_client/util/test_results/bounds.py @@ -43,27 +43,29 @@ def evaluate_measurement_bounds( True if the value is within the bounds, False otherwise. """ assign_value_to_measurement(measurement, value) - if bounds is not None: - if isinstance(bounds, dict): - bounds = NumericBounds(min=bounds.get("min"), max=bounds.get("max")) - if isinstance(bounds, str): - if not (isinstance(value, str) or isinstance(value, bool)): - raise ValueError("Value must be a string if bounds provided is a string") - measurement.string_expected_value = bounds - if isinstance(value, bool): - measurement.passed = str(value).lower() == str(bounds).lower() - else: - measurement.passed = value == bounds - elif isinstance(bounds, NumericBounds): - measurement.numeric_bounds = bounds - measurement.passed = True - float_value = float(value) - if measurement.numeric_bounds.min is not None: - measurement.passed = ( - measurement.passed and measurement.numeric_bounds.min <= float_value - ) - if measurement.numeric_bounds.max is not None: - measurement.passed = ( - measurement.passed and measurement.numeric_bounds.max >= float_value - ) + if bounds is None: + return bool(measurement.passed) + + if isinstance(bounds, dict): + bounds = NumericBounds(min=bounds.get("min"), max=bounds.get("max")) + if isinstance(bounds, str): + if not (isinstance(value, str) or isinstance(value, bool)): + raise ValueError("Value must be a string if bounds provided is a string") + measurement.string_expected_value = bounds + if isinstance(value, bool): + measurement.passed = str(value).lower() == str(bounds).lower() + else: + measurement.passed = value == bounds + elif isinstance(bounds, NumericBounds): + measurement.numeric_bounds = bounds + measurement.passed = True + float_value = float(value) + if measurement.numeric_bounds.min is not None: + measurement.passed = ( + measurement.passed and measurement.numeric_bounds.min <= float_value + ) + if measurement.numeric_bounds.max is not None: + measurement.passed = ( + measurement.passed and measurement.numeric_bounds.max >= float_value + ) return bool(measurement.passed) From 867ba0941531697dc10151fb81724b6825bcf3a8 Mon Sep 17 00:00:00 2001 From: Ian Later Date: Mon, 27 Oct 2025 16:15:58 -0700 Subject: [PATCH 16/16] Limit test_find_run --- python/lib/sift_client/_tests/resources/test_runs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/lib/sift_client/_tests/resources/test_runs.py b/python/lib/sift_client/_tests/resources/test_runs.py index 798555635..40cce6323 100644 --- a/python/lib/sift_client/_tests/resources/test_runs.py +++ b/python/lib/sift_client/_tests/resources/test_runs.py @@ -182,7 +182,11 @@ class TestFind: async def test_find_run(self, runs_api_async, test_run): """Test finding a single run.""" # Find the same run by name - found_run = await runs_api_async.find(name=test_run.name) + found_run = await runs_api_async.find( + name=test_run.name, + created_after=test_run.created_date - timedelta(seconds=10), + created_before=test_run.created_date + timedelta(seconds=10), + ) assert found_run is not None assert found_run.id_ == test_run.id_