refactor(ci): classify failures and pin webgpu inputs
This commit is contained in:
1 parent
c39660737e
commit
543aa55434
12 files changed
+456
-5
No files matched your search
@@ -456,7 +456,8 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --quiet pillow
|
||||
python scripts/compare_render_backends.py `
|
||||
artifacts/render-parity/vulkan artifacts/render-parity/webgpu
|
||||
artifacts/render-parity/vulkan artifacts/render-parity/webgpu `
|
||||
--result-json artifacts/render-parity/ci-result.json
|
||||
|
||||
- name: Upload render parity artifacts
|
||||
if: always()
|
||||
@@ -1495,6 +1496,9 @@ jobs:
|
||||
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.webgpu == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
EMSCRIPTEN_VERSION: "6.0.9"
|
||||
EMDAWNWEBGPU_RELEASE: "v20260423.175430"
|
||||
# Do not add sccache via EM_COMPILER_WRAPPER here: emsdk's Clang commands
|
||||
# carry -Xclang flags that sccache v0.16 treats as non-cacheable, adding
|
||||
# wrapper overhead without storing any of the engine's object files.
|
||||
@@ -1540,7 +1544,7 @@ jobs:
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: $HOME/emsdk
|
||||
key: emsdk-${{ runner.os }}-${{ runner.arch }}-latest
|
||||
key: emsdk-${{ runner.os }}-${{ runner.arch }}-${{ env.EMSCRIPTEN_VERSION }}-${{ env.EMDAWNWEBGPU_RELEASE }}-v1
|
||||
|
||||
- name: Install Emscripten SDK
|
||||
timeout-minutes: 30
|
||||
@@ -1550,8 +1554,46 @@ jobs:
|
||||
else
|
||||
git clone --depth 1 https://github.com/emscripten-core/emsdk.git "$HOME/emsdk"
|
||||
fi
|
||||
"$HOME/emsdk/emsdk" install latest
|
||||
"$HOME/emsdk/emsdk" activate latest
|
||||
"$HOME/emsdk/emsdk" install "$EMSCRIPTEN_VERSION"
|
||||
"$HOME/emsdk/emsdk" activate "$EMSCRIPTEN_VERSION"
|
||||
|
||||
- name: Acquire and verify immutable WebGPU port
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p artifacts/webgpu-dependencies
|
||||
archive="artifacts/webgpu-dependencies/emdawnwebgpu.zip"
|
||||
url="https://github.com/google/dawn/releases/download/${EMDAWNWEBGPU_RELEASE}/emdawnwebgpu_pkg-${EMDAWNWEBGPU_RELEASE}.zip"
|
||||
if ! curl --fail --location --retry 4 --retry-all-errors --retry-delay 5 \
|
||||
--output "$archive" "$url"; then
|
||||
python3 -c 'from pathlib import Path; from scripts.ci_result import CIResult, FailureKind; CIResult.failure(FailureKind.INFRASTRUCTURE, "WEBGPU_DEPENDENCY_DOWNLOAD_FAILED", "webgpu-dependencies", "failed to download the locked emdawnwebgpu archive after retries").write(Path("artifacts/webgpu-dependencies/ci-result.json"))'
|
||||
exit 1
|
||||
fi
|
||||
python3 scripts/verify_webgpu_dependencies.py \
|
||||
--lock cmake/webgpu_dependencies.json \
|
||||
--archive "$archive" \
|
||||
--emscripten-version "$EMSCRIPTEN_VERSION" \
|
||||
--result-json artifacts/webgpu-dependencies/ci-result.json
|
||||
|
||||
- name: Prewarm Emscripten WebGPU port
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source "$HOME/emsdk/emsdk_env.sh"
|
||||
for attempt in 1 2 3; do
|
||||
em++ --use-port=emdawnwebgpu -x c++ -c /dev/null \
|
||||
-o artifacts/webgpu-dependencies/port-probe.o && exit 0
|
||||
[ "$attempt" -lt 3 ] && sleep $((attempt * 10))
|
||||
done
|
||||
exit 1
|
||||
|
||||
- name: Upload WebGPU dependency result
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: webgpu-dependency-result
|
||||
path: artifacts/webgpu-dependencies/ci-result.json
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Configure webgpu (Emscripten/WASM)
|
||||
timeout-minutes: 15
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"schema": "evengine.webgpu-dependencies",
|
||||
"version": 1,
|
||||
"emscripten": "6.0.9",
|
||||
"emdawnwebgpu": {
|
||||
"release": "v20260423.175430",
|
||||
"url": "https://github.com/google/dawn/releases/download/v20260423.175430/emdawnwebgpu_pkg-v20260423.175430.zip",
|
||||
"sha256": "3ad7ebe95a4a20764df46c5a63b0ad489782aee26804fff8511d9797c10e732f"
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
Submodule external/zeroerr updated: 43c31f64ca...0a59a30f01.
@@ -0,0 +1,59 @@
|
||||
"""Versioned machine-readable outcomes shared by EVEngine CI tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class FailureKind(StrEnum):
|
||||
"""Stable top-level CI failure categories; values are public protocol data."""
|
||||
|
||||
COMPILE = "compile"
|
||||
LINK = "link"
|
||||
CONTRACT = "contract"
|
||||
TEST_ASSERTION = "test_assertion"
|
||||
SANITIZER = "sanitizer"
|
||||
INFRASTRUCTURE = "infrastructure"
|
||||
PARITY = "parity"
|
||||
PUBLISH_CONFLICT = "publish_conflict"
|
||||
CANCELLED_SUPERSEDED = "cancelled_superseded"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CIResult:
|
||||
"""One owning, serializable CI outcome with a stable schema."""
|
||||
|
||||
ok: bool
|
||||
kind: str | None = None
|
||||
code: str | None = None
|
||||
subject: str | None = None
|
||||
message: str | None = None
|
||||
details: dict[str, Any] | None = None
|
||||
schema: str = "evengine.ci-result"
|
||||
version: int = 1
|
||||
|
||||
@classmethod
|
||||
def success(cls, subject: str) -> CIResult:
|
||||
return cls(ok=True, subject=subject)
|
||||
|
||||
@classmethod
|
||||
def failure(
|
||||
cls,
|
||||
kind: FailureKind,
|
||||
code: str,
|
||||
subject: str,
|
||||
message: str,
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> CIResult:
|
||||
return cls(False, kind.value, code, subject, message, details)
|
||||
|
||||
def write(self, path: Path) -> None:
|
||||
"""Atomically publish this result so partial JSON is never observable."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(path.suffix + ".tmp")
|
||||
temporary.write_text(json.dumps(asdict(self), indent=2) + "\n", encoding="utf-8")
|
||||
temporary.replace(path)
|
||||
@@ -13,6 +13,11 @@ from typing import Any
|
||||
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
from scripts.ci_result import CIResult, FailureKind
|
||||
except ModuleNotFoundError: # Direct execution sets sys.path to scripts/.
|
||||
from ci_result import CIResult, FailureKind
|
||||
|
||||
|
||||
class ArtifactContractError(ValueError):
|
||||
"""Raised when render-parity producers emitted incomplete artifact sets."""
|
||||
@@ -185,6 +190,7 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("reference", type=Path, help="Vulkan artifact directory")
|
||||
parser.add_argument("candidate", type=Path, help="WebGPU artifact directory")
|
||||
parser.add_argument("--result-json", type=Path, help="write a versioned CI result")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
@@ -192,6 +198,13 @@ def main() -> int:
|
||||
except ArtifactContractError as error:
|
||||
print("ARTIFACT CONTRACT FAILURE:", file=sys.stderr)
|
||||
print(error, file=sys.stderr)
|
||||
if args.result_json:
|
||||
CIResult.failure(
|
||||
FailureKind.CONTRACT,
|
||||
"RENDER_ARTIFACT_CONTRACT_INVALID",
|
||||
"render-parity",
|
||||
str(error),
|
||||
).write(args.result_json)
|
||||
return 2
|
||||
failures: list[str] = []
|
||||
for manifest in manifests:
|
||||
@@ -199,7 +212,17 @@ def main() -> int:
|
||||
if failures:
|
||||
print("PIXEL PARITY FAILURE:", file=sys.stderr)
|
||||
print("\n".join(f"ERROR: {failure}" for failure in failures), file=sys.stderr)
|
||||
if args.result_json:
|
||||
CIResult.failure(
|
||||
FailureKind.PARITY,
|
||||
"RENDER_PARITY_EXCEEDED",
|
||||
"render-parity",
|
||||
"one or more scenes exceeded their render contract",
|
||||
{"failures": failures},
|
||||
).write(args.result_json)
|
||||
return 1
|
||||
if args.result_json:
|
||||
CIResult.success("render-parity").write(args.result_json)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.ci_result import CIResult, FailureKind
|
||||
|
||||
|
||||
class CIResultTest(unittest.TestCase):
|
||||
def test_failure_has_stable_schema_and_kind(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / "nested" / "result.json"
|
||||
CIResult.failure(
|
||||
FailureKind.PARITY,
|
||||
"RENDER_MEAN_RGB_EXCEEDED",
|
||||
"scene",
|
||||
"mean RGB exceeded contract",
|
||||
{"expected": 0.1, "actual": 0.2},
|
||||
).write(path)
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
self.assertEqual(payload["schema"], "evengine.ci-result")
|
||||
self.assertEqual(payload["version"], 1)
|
||||
self.assertEqual(payload["kind"], "parity")
|
||||
self.assertEqual(payload["code"], "RENDER_MEAN_RGB_EXCEEDED")
|
||||
|
||||
def test_success_has_no_failure_classification(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / "result.json"
|
||||
CIResult.success("render-parity").write(path)
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
self.assertTrue(payload["ok"])
|
||||
self.assertIsNone(payload["kind"])
|
||||
self.assertIsNone(payload["code"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,51 @@
|
||||
import hashlib
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.verify_webgpu_dependencies import DependencyContractError, verify
|
||||
|
||||
|
||||
class VerifyWebgpuDependenciesTest(unittest.TestCase):
|
||||
def fixture(self, root: Path) -> tuple[Path, Path]:
|
||||
archive = root / "port.zip"
|
||||
archive.write_bytes(b"immutable-port")
|
||||
lock = root / "lock.json"
|
||||
lock.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema": "evengine.webgpu-dependencies",
|
||||
"version": 1,
|
||||
"emscripten": "6.0.9",
|
||||
"emdawnwebgpu": {
|
||||
"release": "v1",
|
||||
"sha256": hashlib.sha256(b"immutable-port").hexdigest(),
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return lock, archive
|
||||
|
||||
def test_matching_identity_passes(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
lock, archive = self.fixture(Path(temporary))
|
||||
self.assertEqual(verify(lock, archive, "6.0.9")["version"], 1)
|
||||
|
||||
def test_version_drift_is_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
lock, archive = self.fixture(Path(temporary))
|
||||
with self.assertRaisesRegex(DependencyContractError, "version mismatch"):
|
||||
verify(lock, archive, "latest")
|
||||
|
||||
def test_content_drift_is_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
lock, archive = self.fixture(Path(temporary))
|
||||
archive.write_bytes(b"tampered")
|
||||
with self.assertRaisesRegex(DependencyContractError, "SHA-256 mismatch"):
|
||||
verify(lock, archive, "6.0.9")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify immutable WebGPU toolchain inputs before compilation starts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from scripts.ci_result import CIResult, FailureKind
|
||||
except ModuleNotFoundError: # Direct execution sets sys.path to scripts/.
|
||||
from ci_result import CIResult, FailureKind
|
||||
|
||||
|
||||
class DependencyContractError(ValueError):
|
||||
"""The acquired dependency does not match the repository lock."""
|
||||
|
||||
|
||||
def verify(lock_path: Path, archive_path: Path, emscripten_version: str) -> dict:
|
||||
lock = json.loads(lock_path.read_text(encoding="utf-8"))
|
||||
if lock.get("schema") != "evengine.webgpu-dependencies" or lock.get("version") != 1:
|
||||
raise DependencyContractError("unsupported WebGPU dependency lock schema")
|
||||
if emscripten_version != lock["emscripten"]:
|
||||
raise DependencyContractError(
|
||||
f"Emscripten version mismatch: {emscripten_version} != {lock['emscripten']}"
|
||||
)
|
||||
actual = hashlib.sha256(archive_path.read_bytes()).hexdigest()
|
||||
expected = lock["emdawnwebgpu"]["sha256"]
|
||||
if actual != expected:
|
||||
raise DependencyContractError(
|
||||
f"emdawnwebgpu SHA-256 mismatch: {actual} != {expected}"
|
||||
)
|
||||
return lock
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--lock", type=Path, required=True)
|
||||
parser.add_argument("--archive", type=Path, required=True)
|
||||
parser.add_argument("--emscripten-version", required=True)
|
||||
parser.add_argument("--result-json", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
lock = verify(args.lock, args.archive, args.emscripten_version)
|
||||
except (OSError, KeyError, json.JSONDecodeError, DependencyContractError) as error:
|
||||
if args.result_json:
|
||||
CIResult.failure(
|
||||
FailureKind.INFRASTRUCTURE,
|
||||
"WEBGPU_DEPENDENCY_VERIFICATION_FAILED",
|
||||
"webgpu-dependencies",
|
||||
str(error),
|
||||
).write(args.result_json)
|
||||
print(f"WEBGPU DEPENDENCY FAILURE: {error}")
|
||||
return 2
|
||||
if args.result_json:
|
||||
CIResult.success("webgpu-dependencies").write(args.result_json)
|
||||
print(
|
||||
f"WebGPU dependencies verified: Emscripten {lock['emscripten']}, "
|
||||
f"emdawnwebgpu {lock['emdawnwebgpu']['release']}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file DiagnosticValue.h
|
||||
* @brief Compile-time contract for stable names and codes of domain values.
|
||||
*/
|
||||
|
||||
#include "common/Diagnostic.h"
|
||||
#include "common/Status.h"
|
||||
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace eve {
|
||||
|
||||
/** @brief Customization point for values that cross diagnostic boundaries. */
|
||||
template <class T>
|
||||
struct DiagnosticValueTraits;
|
||||
|
||||
/**
|
||||
* @brief A value with a stable protocol name and machine-readable diagnostic code.
|
||||
* @note Specializations must be pure, thread-safe, and return static text.
|
||||
*/
|
||||
template <class T>
|
||||
concept DiagnosticValue = std::is_enum_v<T> && requires(T value) {
|
||||
{ DiagnosticValueTraits<T>::name(value) } noexcept -> std::convertible_to<std::string_view>;
|
||||
{ DiagnosticValueTraits<T>::code(value) } noexcept -> std::same_as<DiagnosticCode>;
|
||||
};
|
||||
|
||||
/** @brief Return the stable name supplied by a DiagnosticValue specialization. */
|
||||
template <DiagnosticValue T>
|
||||
[[nodiscard]] constexpr std::string_view diagnosticValueName(T value) noexcept {
|
||||
return DiagnosticValueTraits<T>::name(value);
|
||||
}
|
||||
|
||||
/** @brief Return the stable machine-readable code supplied by a DiagnosticValue specialization. */
|
||||
template <DiagnosticValue T>
|
||||
[[nodiscard]] constexpr DiagnosticCode diagnosticValueCode(T value) noexcept {
|
||||
return DiagnosticValueTraits<T>::code(value);
|
||||
}
|
||||
|
||||
/** @brief DiagnosticValue mapping for diagnostic severities. */
|
||||
template <>
|
||||
struct DiagnosticValueTraits<Severity> {
|
||||
static constexpr std::string_view name(Severity value) noexcept { return severityName(value); }
|
||||
static constexpr DiagnosticCode code(Severity value) noexcept {
|
||||
return value == Severity::Error || value == Severity::Fatal ? DiagnosticCode::Failed : DiagnosticCode::None;
|
||||
}
|
||||
};
|
||||
|
||||
/** @brief DiagnosticValue mapping for machine-readable diagnostic codes. */
|
||||
template <>
|
||||
struct DiagnosticValueTraits<DiagnosticCode> {
|
||||
static constexpr std::string_view name(DiagnosticCode value) noexcept { return diagnosticCodeName(value); }
|
||||
static constexpr DiagnosticCode code(DiagnosticCode value) noexcept { return value; }
|
||||
};
|
||||
|
||||
/** @brief DiagnosticValue mapping for operation status categories. */
|
||||
template <>
|
||||
struct DiagnosticValueTraits<StatusCode> {
|
||||
static constexpr std::string_view name(StatusCode value) noexcept { return statusCodeName(value); }
|
||||
static constexpr DiagnosticCode code(StatusCode value) noexcept {
|
||||
switch (value) {
|
||||
case StatusCode::Ok:
|
||||
case StatusCode::Applied:
|
||||
case StatusCode::NoOp:
|
||||
case StatusCode::Pending: return DiagnosticCode::None;
|
||||
case StatusCode::Rejected: return DiagnosticCode::PreconditionViolation;
|
||||
case StatusCode::Conflict: return DiagnosticCode::Conflict;
|
||||
case StatusCode::NotFound: return DiagnosticCode::NotFound;
|
||||
case StatusCode::Unsupported: return DiagnosticCode::Unsupported;
|
||||
case StatusCode::Cancelled: return DiagnosticCode::Cancelled;
|
||||
case StatusCode::Failed: return DiagnosticCode::Failed;
|
||||
}
|
||||
return DiagnosticCode::Failed;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace eve
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file ProtocolDispatch.h
|
||||
* @brief Exhaustive outcomes for one LSP, DAP, or MCP input message.
|
||||
*/
|
||||
|
||||
#include "common/DiagnosticValue.h"
|
||||
#include "common/Result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace eve {
|
||||
|
||||
/** @brief Observable disposition of one protocol input message. */
|
||||
enum class ProtocolDispatch {
|
||||
ReplySent,
|
||||
NotificationHandled,
|
||||
Rejected,
|
||||
Terminate,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Structured result returned by protocol dispatch boundaries.
|
||||
* @note Transport/parse failures belong in the Result status; successful
|
||||
* values describe every valid protocol disposition exhaustively.
|
||||
*/
|
||||
using ProtocolDispatchResult = Result<ProtocolDispatch>;
|
||||
|
||||
/** @brief DiagnosticValue mapping for protocol dispatch outcomes. */
|
||||
template <>
|
||||
struct DiagnosticValueTraits<ProtocolDispatch> {
|
||||
static constexpr std::string_view name(ProtocolDispatch value) noexcept {
|
||||
switch (value) {
|
||||
case ProtocolDispatch::ReplySent: return "reply_sent";
|
||||
case ProtocolDispatch::NotificationHandled: return "notification_handled";
|
||||
case ProtocolDispatch::Rejected: return "rejected";
|
||||
case ProtocolDispatch::Terminate: return "terminate";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static constexpr DiagnosticCode code(ProtocolDispatch value) noexcept {
|
||||
return value == ProtocolDispatch::Rejected ? DiagnosticCode::PreconditionViolation : DiagnosticCode::None;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace eve
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "common/DiagnosticValue.h"
|
||||
|
||||
#include "zeroerr/assert.h"
|
||||
#include "zeroerr/unittest.h"
|
||||
|
||||
namespace {
|
||||
enum class UnregisteredValue { Value };
|
||||
}
|
||||
|
||||
static_assert(eve::DiagnosticValue<eve::Severity>);
|
||||
static_assert(eve::DiagnosticValue<eve::DiagnosticCode>);
|
||||
static_assert(eve::DiagnosticValue<eve::StatusCode>);
|
||||
static_assert(!eve::DiagnosticValue<UnregisteredValue>);
|
||||
|
||||
TEST_CASE("common.diagnosticValue.stableNamesAndCodes") {
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::Severity::Fatal), std::string_view("fatal"));
|
||||
CHECK_EQ(eve::diagnosticValueCode(eve::Severity::Fatal), eve::DiagnosticCode::Failed);
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::StatusCode::Conflict), std::string_view("conflict"));
|
||||
CHECK_EQ(eve::diagnosticValueCode(eve::StatusCode::Conflict), eve::DiagnosticCode::Conflict);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "common/ProtocolDispatch.h"
|
||||
|
||||
#include "zeroerr/unittest.h"
|
||||
|
||||
static_assert(eve::DiagnosticValue<eve::ProtocolDispatch>);
|
||||
|
||||
TEST_CASE("common.protocolDispatch.hasExhaustiveStableOutcomes") {
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::ProtocolDispatch::ReplySent), std::string_view("reply_sent"));
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::ProtocolDispatch::NotificationHandled),
|
||||
std::string_view("notification_handled"));
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::ProtocolDispatch::Rejected), std::string_view("rejected"));
|
||||
CHECK_EQ(eve::diagnosticValueName(eve::ProtocolDispatch::Terminate), std::string_view("terminate"));
|
||||
CHECK_EQ(eve::diagnosticValueCode(eve::ProtocolDispatch::Rejected), eve::DiagnosticCode::PreconditionViolation);
|
||||
}
|
||||
Reference in new issue
Block a user