mirror of
https://github.com/lexmount/moli.git
synced 2026-10-02 00:00:49 +00:00
785 lines
23 KiB
Rust
785 lines
23 KiB
Rust
//! Runtime-independent `TransformStream` coordination.
|
|
//!
|
|
//! The renderer owns JavaScript chunks, reasons, callbacks, promises, and the
|
|
//! controller's finish promise/resolver pair. This module coordinates start,
|
|
//! write, backpressure, and admission to a shared finish operation. The renderer
|
|
//! applies cancel/abort/flush settlement in the corresponding promise reaction.
|
|
|
|
use crate::readable::ReadableState;
|
|
use crate::strategy::StrategySnapshot;
|
|
use crate::writable::WritableState;
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformMode {
|
|
Identity,
|
|
Callback,
|
|
TextEncoder,
|
|
TextDecoder,
|
|
}
|
|
|
|
impl TransformMode {
|
|
#[must_use]
|
|
pub fn from_storage(mode: Option<&str>, has_transformer: bool) -> Self {
|
|
match mode {
|
|
Some("text-encoder") => Self::TextEncoder,
|
|
Some("text-decoder") => Self::TextDecoder,
|
|
_ if has_transformer => Self::Callback,
|
|
_ => Self::Identity,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn write_algorithm(self) -> TransformWriteAlgorithm {
|
|
match self {
|
|
Self::Identity => TransformWriteAlgorithm::Identity,
|
|
Self::Callback => TransformWriteAlgorithm::Callback,
|
|
Self::TextEncoder => TransformWriteAlgorithm::TextEncoder,
|
|
Self::TextDecoder => TransformWriteAlgorithm::TextDecoder,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn flush_algorithm(self) -> TransformFlushAlgorithm {
|
|
match self {
|
|
Self::Callback => TransformFlushAlgorithm::Callback,
|
|
Self::TextDecoder => TransformFlushAlgorithm::TextDecoder,
|
|
Self::Identity | Self::TextEncoder => TransformFlushAlgorithm::None,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn cancel_algorithm(self) -> TransformCancelAlgorithm {
|
|
match self {
|
|
Self::Callback => TransformCancelAlgorithm::Callback,
|
|
Self::Identity | Self::TextEncoder | Self::TextDecoder => {
|
|
TransformCancelAlgorithm::None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformWriteAlgorithm {
|
|
Identity,
|
|
Callback,
|
|
TextEncoder,
|
|
TextDecoder,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformFlushAlgorithm {
|
|
None,
|
|
Callback,
|
|
TextDecoder,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformCancelAlgorithm {
|
|
None,
|
|
Callback,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct TransformReadableSnapshot {
|
|
state: ReadableState,
|
|
pending_read_count: usize,
|
|
pipe_registered: bool,
|
|
strategy: StrategySnapshot,
|
|
}
|
|
|
|
impl TransformReadableSnapshot {
|
|
#[must_use]
|
|
pub const fn new(
|
|
state: ReadableState,
|
|
pending_read_count: usize,
|
|
pipe_registered: bool,
|
|
strategy: StrategySnapshot,
|
|
) -> Self {
|
|
Self {
|
|
state,
|
|
pending_read_count,
|
|
pipe_registered,
|
|
strategy,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn state(self) -> ReadableState {
|
|
self.state
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn pending_read_count(self) -> usize {
|
|
self.pending_read_count
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn pipe_registered(self) -> bool {
|
|
self.pipe_registered
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn backpressure(self) -> TransformBackpressure {
|
|
if !matches!(self.state, ReadableState::Readable) {
|
|
return TransformBackpressure::Terminal;
|
|
}
|
|
if self.pending_read_count > 0 || self.pipe_registered || self.strategy.has_capacity() {
|
|
TransformBackpressure::Ready
|
|
} else {
|
|
TransformBackpressure::Backpressured
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn can_accept_chunk(self) -> bool {
|
|
matches!(self.backpressure(), TransformBackpressure::Ready)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformBackpressure {
|
|
Ready,
|
|
Backpressured,
|
|
Terminal,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum FinishResidenceState {
|
|
Available,
|
|
Claimed,
|
|
}
|
|
|
|
impl FinishResidenceState {
|
|
#[must_use]
|
|
pub const fn from_storage(has_finish_promise: bool) -> Self {
|
|
if has_finish_promise {
|
|
Self::Claimed
|
|
} else {
|
|
Self::Available
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct TransformSnapshot {
|
|
readable: TransformReadableSnapshot,
|
|
writable_state: WritableState,
|
|
mode: TransformMode,
|
|
start_pending: bool,
|
|
pending_operation_count: usize,
|
|
finish: FinishResidenceState,
|
|
}
|
|
|
|
impl TransformSnapshot {
|
|
#[must_use]
|
|
pub const fn new(
|
|
readable: TransformReadableSnapshot,
|
|
writable_state: WritableState,
|
|
mode: TransformMode,
|
|
start_pending: bool,
|
|
pending_operation_count: usize,
|
|
finish: FinishResidenceState,
|
|
) -> Self {
|
|
Self {
|
|
readable,
|
|
writable_state,
|
|
mode,
|
|
start_pending,
|
|
pending_operation_count,
|
|
finish,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn readable(self) -> TransformReadableSnapshot {
|
|
self.readable
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn writable_state(self) -> WritableState {
|
|
self.writable_state
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn mode(self) -> TransformMode {
|
|
self.mode
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn plan_write_admission(self) -> TransformWriteAdmissionPlan {
|
|
if matches!(self.mode, TransformMode::Callback)
|
|
|| self.start_pending
|
|
|| self.pending_operation_count > 0
|
|
|| !self.readable.can_accept_chunk()
|
|
{
|
|
TransformWriteAdmissionPlan::Queue
|
|
} else {
|
|
TransformWriteAdmissionPlan::Run(self.mode.write_algorithm())
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_queued_write_algorithm(self) -> TransformWriteAlgorithm {
|
|
self.mode.write_algorithm()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_close_admission(self) -> TransformCloseAdmissionPlan {
|
|
if self.start_pending || self.pending_operation_count > 0 {
|
|
TransformCloseAdmissionPlan::Queue
|
|
} else {
|
|
TransformCloseAdmissionPlan::Run
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_finish(self, operation: FinishOperation) -> FinishClaimPlan {
|
|
if matches!(self.finish, FinishResidenceState::Claimed) {
|
|
return FinishClaimPlan::Reuse;
|
|
}
|
|
let algorithm = match operation {
|
|
FinishOperation::WritableClose => FinishAlgorithm::Flush(self.mode.flush_algorithm()),
|
|
FinishOperation::ReadableCancel | FinishOperation::WritableAbort => {
|
|
FinishAlgorithm::Cancel(self.mode.cancel_algorithm())
|
|
}
|
|
};
|
|
FinishClaimPlan::Claim { algorithm }
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_start_settlement(self, outcome: AlgorithmOutcome) -> StartSettlementPlan {
|
|
match outcome {
|
|
AlgorithmOutcome::Fulfilled => StartSettlementPlan::ClearPendingAndPump,
|
|
AlgorithmOutcome::Rejected => StartSettlementPlan::ClearPendingAndErrorBoth,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_write_settlement(
|
|
self,
|
|
outcome: AlgorithmOutcome,
|
|
direct_write_size: bool,
|
|
) -> WriteSettlementPlan {
|
|
match outcome {
|
|
AlgorithmOutcome::Fulfilled => WriteSettlementPlan::Fulfill {
|
|
finish_direct_write: direct_write_size,
|
|
drain_pipe: direct_write_size,
|
|
},
|
|
AlgorithmOutcome::Rejected => WriteSettlementPlan::Reject {
|
|
finish_direct_write: direct_write_size,
|
|
error: self.plan_error(),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_writable_close_settlement(
|
|
self,
|
|
outcome: AlgorithmOutcome,
|
|
) -> WritableCloseSettlementPlan {
|
|
match outcome {
|
|
AlgorithmOutcome::Fulfilled => WritableCloseSettlementPlan::MarkClosed,
|
|
AlgorithmOutcome::Rejected => {
|
|
WritableCloseSettlementPlan::ClearInFlightAndErrorWritable
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_error(self) -> TransformErrorPlan {
|
|
match self.readable.state {
|
|
ReadableState::Readable => TransformErrorPlan {
|
|
reason: ErrorReasonSource::Provided,
|
|
readable: ReadableErrorAction::Error,
|
|
},
|
|
ReadableState::Closed => TransformErrorPlan {
|
|
reason: ErrorReasonSource::Provided,
|
|
readable: ReadableErrorAction::Keep,
|
|
},
|
|
ReadableState::Errored => TransformErrorPlan {
|
|
reason: ErrorReasonSource::ReadableStored,
|
|
readable: ReadableErrorAction::Keep,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_terminate(self) -> TerminatePlan {
|
|
TerminatePlan {
|
|
readable: if matches!(self.readable.state, ReadableState::Readable) {
|
|
ReadableTerminateAction::Close
|
|
} else {
|
|
ReadableTerminateAction::Keep
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_finish_setup_failure(
|
|
self,
|
|
operation: FinishOperation,
|
|
) -> FinishSetupFailurePlan {
|
|
match operation {
|
|
FinishOperation::ReadableCancel => {
|
|
FinishSetupFailurePlan::ErrorWritableWithOriginalReasonAndReject
|
|
}
|
|
FinishOperation::WritableAbort => {
|
|
FinishSetupFailurePlan::ErrorReadableWithOriginalReasonAndReject
|
|
}
|
|
FinishOperation::WritableClose => {
|
|
FinishSetupFailurePlan::ErrorReadableWithUndefinedAndReject
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn plan_enqueue_failure(
|
|
self,
|
|
failure: TransformEnqueueFailure,
|
|
) -> TransformEnqueueFailurePlan {
|
|
match failure {
|
|
TransformEnqueueFailure::ClosedOrErrored => TransformEnqueueFailurePlan {
|
|
returned_error: EnqueueErrorSource::SynthesizedTypeError,
|
|
propagation: self.plan_error(),
|
|
},
|
|
TransformEnqueueFailure::Strategy => {
|
|
let propagation = self.plan_error();
|
|
TransformEnqueueFailurePlan {
|
|
returned_error: match propagation.reason {
|
|
ErrorReasonSource::Provided => EnqueueErrorSource::Provided,
|
|
ErrorReasonSource::ReadableStored => EnqueueErrorSource::ReadableStored,
|
|
},
|
|
propagation,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformWriteAdmissionPlan {
|
|
Queue,
|
|
Run(TransformWriteAlgorithm),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformCloseAdmissionPlan {
|
|
Queue,
|
|
Run,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum FinishOperation {
|
|
ReadableCancel,
|
|
WritableAbort,
|
|
WritableClose,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum FinishAlgorithm {
|
|
Cancel(TransformCancelAlgorithm),
|
|
Flush(TransformFlushAlgorithm),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum FinishClaimPlan {
|
|
Reuse,
|
|
Claim { algorithm: FinishAlgorithm },
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum AlgorithmOutcome {
|
|
Fulfilled,
|
|
Rejected,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum StartSettlementPlan {
|
|
ClearPendingAndPump,
|
|
ClearPendingAndErrorBoth,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum WriteSettlementPlan {
|
|
Fulfill {
|
|
finish_direct_write: bool,
|
|
drain_pipe: bool,
|
|
},
|
|
Reject {
|
|
finish_direct_write: bool,
|
|
error: TransformErrorPlan,
|
|
},
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum WritableCloseSettlementPlan {
|
|
MarkClosed,
|
|
ClearInFlightAndErrorWritable,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum ErrorReasonSource {
|
|
Provided,
|
|
ReadableStored,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum ReadableErrorAction {
|
|
Keep,
|
|
Error,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub struct TransformErrorPlan {
|
|
reason: ErrorReasonSource,
|
|
readable: ReadableErrorAction,
|
|
}
|
|
|
|
impl TransformErrorPlan {
|
|
#[must_use]
|
|
pub const fn reason(self) -> ErrorReasonSource {
|
|
self.reason
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn readable(self) -> ReadableErrorAction {
|
|
self.readable
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum ReadableTerminateAction {
|
|
Keep,
|
|
Close,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub struct TerminatePlan {
|
|
readable: ReadableTerminateAction,
|
|
}
|
|
|
|
impl TerminatePlan {
|
|
#[must_use]
|
|
pub const fn readable(self) -> ReadableTerminateAction {
|
|
self.readable
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum FinishSetupFailurePlan {
|
|
ErrorWritableWithOriginalReasonAndReject,
|
|
ErrorReadableWithOriginalReasonAndReject,
|
|
ErrorReadableWithUndefinedAndReject,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum TransformEnqueueFailure {
|
|
ClosedOrErrored,
|
|
Strategy,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum EnqueueErrorSource {
|
|
SynthesizedTypeError,
|
|
Provided,
|
|
ReadableStored,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub struct TransformEnqueueFailurePlan {
|
|
returned_error: EnqueueErrorSource,
|
|
propagation: TransformErrorPlan,
|
|
}
|
|
|
|
impl TransformEnqueueFailurePlan {
|
|
#[must_use]
|
|
pub const fn returned_error(self) -> EnqueueErrorSource {
|
|
self.returned_error
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn propagation(self) -> TransformErrorPlan {
|
|
self.propagation
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn readable(
|
|
state: ReadableState,
|
|
pending_reads: usize,
|
|
pipe_registered: bool,
|
|
high_water_mark: f64,
|
|
total_size: f64,
|
|
) -> TransformReadableSnapshot {
|
|
TransformReadableSnapshot::new(
|
|
state,
|
|
pending_reads,
|
|
pipe_registered,
|
|
StrategySnapshot::new(high_water_mark, total_size),
|
|
)
|
|
}
|
|
|
|
fn snapshot(
|
|
readable: TransformReadableSnapshot,
|
|
writable_state: WritableState,
|
|
mode: TransformMode,
|
|
) -> TransformSnapshot {
|
|
TransformSnapshot::new(
|
|
readable,
|
|
writable_state,
|
|
mode,
|
|
false,
|
|
0,
|
|
FinishResidenceState::Available,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn storage_mode_owns_write_flush_and_cancel_routing() {
|
|
let cases = [
|
|
(
|
|
None,
|
|
false,
|
|
TransformMode::Identity,
|
|
TransformWriteAlgorithm::Identity,
|
|
TransformFlushAlgorithm::None,
|
|
TransformCancelAlgorithm::None,
|
|
),
|
|
(
|
|
None,
|
|
true,
|
|
TransformMode::Callback,
|
|
TransformWriteAlgorithm::Callback,
|
|
TransformFlushAlgorithm::Callback,
|
|
TransformCancelAlgorithm::Callback,
|
|
),
|
|
(
|
|
Some("text-encoder"),
|
|
true,
|
|
TransformMode::TextEncoder,
|
|
TransformWriteAlgorithm::TextEncoder,
|
|
TransformFlushAlgorithm::None,
|
|
TransformCancelAlgorithm::None,
|
|
),
|
|
(
|
|
Some("text-decoder"),
|
|
false,
|
|
TransformMode::TextDecoder,
|
|
TransformWriteAlgorithm::TextDecoder,
|
|
TransformFlushAlgorithm::TextDecoder,
|
|
TransformCancelAlgorithm::None,
|
|
),
|
|
];
|
|
for (stored, has_transformer, mode, write, flush, cancel) in cases {
|
|
let decoded = TransformMode::from_storage(stored, has_transformer);
|
|
assert_eq!(decoded, mode);
|
|
assert_eq!(decoded.write_algorithm(), write);
|
|
assert_eq!(decoded.flush_algorithm(), flush);
|
|
assert_eq!(decoded.cancel_algorithm(), cancel);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn readable_backpressure_combines_lifecycle_demand_pipe_and_strategy() {
|
|
assert_eq!(
|
|
readable(ReadableState::Closed, 1, true, 1.0, 0.0).backpressure(),
|
|
TransformBackpressure::Terminal
|
|
);
|
|
assert_eq!(
|
|
readable(ReadableState::Readable, 1, false, 0.0, 0.0).backpressure(),
|
|
TransformBackpressure::Ready
|
|
);
|
|
assert_eq!(
|
|
readable(ReadableState::Readable, 0, true, 0.0, 0.0).backpressure(),
|
|
TransformBackpressure::Ready
|
|
);
|
|
assert_eq!(
|
|
readable(ReadableState::Readable, 0, false, 2.0, 1.0).backpressure(),
|
|
TransformBackpressure::Ready
|
|
);
|
|
assert_eq!(
|
|
readable(ReadableState::Readable, 0, false, 1.0, 1.0).backpressure(),
|
|
TransformBackpressure::Backpressured
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn write_and_close_admission_cover_callback_start_queue_and_capacity() {
|
|
let ready = readable(ReadableState::Readable, 1, false, 0.0, 0.0);
|
|
assert_eq!(
|
|
snapshot(ready, WritableState::Writable, TransformMode::Identity)
|
|
.plan_write_admission(),
|
|
TransformWriteAdmissionPlan::Run(TransformWriteAlgorithm::Identity)
|
|
);
|
|
assert_eq!(
|
|
snapshot(ready, WritableState::Writable, TransformMode::Callback)
|
|
.plan_write_admission(),
|
|
TransformWriteAdmissionPlan::Queue
|
|
);
|
|
|
|
for (start_pending, pending_count, backpressured) in
|
|
[(true, 0, false), (false, 1, false), (false, 0, true)]
|
|
{
|
|
let readable = if backpressured {
|
|
readable(ReadableState::Readable, 0, false, 0.0, 0.0)
|
|
} else {
|
|
ready
|
|
};
|
|
let current = TransformSnapshot::new(
|
|
readable,
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
start_pending,
|
|
pending_count,
|
|
FinishResidenceState::Available,
|
|
);
|
|
assert_eq!(
|
|
current.plan_write_admission(),
|
|
TransformWriteAdmissionPlan::Queue
|
|
);
|
|
assert_eq!(
|
|
current.plan_close_admission(),
|
|
if start_pending || pending_count > 0 {
|
|
TransformCloseAdmissionPlan::Queue
|
|
} else {
|
|
TransformCloseAdmissionPlan::Run
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn finish_residence_is_claimed_once_with_operation_specific_algorithm() {
|
|
let available = snapshot(
|
|
readable(ReadableState::Readable, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Callback,
|
|
);
|
|
assert_eq!(
|
|
available.plan_finish(FinishOperation::ReadableCancel),
|
|
FinishClaimPlan::Claim {
|
|
algorithm: FinishAlgorithm::Cancel(TransformCancelAlgorithm::Callback),
|
|
}
|
|
);
|
|
assert_eq!(
|
|
available.plan_finish(FinishOperation::WritableClose),
|
|
FinishClaimPlan::Claim {
|
|
algorithm: FinishAlgorithm::Flush(TransformFlushAlgorithm::Callback),
|
|
}
|
|
);
|
|
let claimed = TransformSnapshot::new(
|
|
available.readable(),
|
|
available.writable_state(),
|
|
available.mode(),
|
|
false,
|
|
0,
|
|
FinishResidenceState::Claimed,
|
|
);
|
|
assert_eq!(
|
|
claimed.plan_finish(FinishOperation::WritableAbort),
|
|
FinishClaimPlan::Reuse
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn transform_error_preserves_the_first_readable_error() {
|
|
let plan = snapshot(
|
|
readable(ReadableState::Errored, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
)
|
|
.plan_error();
|
|
assert_eq!(plan.reason(), ErrorReasonSource::ReadableStored);
|
|
assert_eq!(plan.readable(), ReadableErrorAction::Keep);
|
|
|
|
let plan = snapshot(
|
|
readable(ReadableState::Readable, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
)
|
|
.plan_error();
|
|
assert_eq!(plan.reason(), ErrorReasonSource::Provided);
|
|
assert_eq!(plan.readable(), ReadableErrorAction::Error);
|
|
}
|
|
|
|
#[test]
|
|
fn setup_failures_and_enqueue_failures_preserve_error_ownership() {
|
|
let current = snapshot(
|
|
readable(ReadableState::Readable, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
);
|
|
assert_eq!(
|
|
current.plan_finish_setup_failure(FinishOperation::ReadableCancel),
|
|
FinishSetupFailurePlan::ErrorWritableWithOriginalReasonAndReject
|
|
);
|
|
assert_eq!(
|
|
current.plan_finish_setup_failure(FinishOperation::WritableClose),
|
|
FinishSetupFailurePlan::ErrorReadableWithUndefinedAndReject
|
|
);
|
|
let closed = current.plan_enqueue_failure(TransformEnqueueFailure::ClosedOrErrored);
|
|
assert_eq!(
|
|
closed.returned_error(),
|
|
EnqueueErrorSource::SynthesizedTypeError
|
|
);
|
|
|
|
let errored = snapshot(
|
|
readable(ReadableState::Errored, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
)
|
|
.plan_enqueue_failure(TransformEnqueueFailure::Strategy);
|
|
assert_eq!(errored.returned_error(), EnqueueErrorSource::ReadableStored);
|
|
assert_eq!(
|
|
errored.propagation().reason(),
|
|
ErrorReasonSource::ReadableStored
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn start_write_and_terminate_plans_are_explicit() {
|
|
let current = snapshot(
|
|
readable(ReadableState::Readable, 0, false, 1.0, 0.0),
|
|
WritableState::Writable,
|
|
TransformMode::Identity,
|
|
);
|
|
assert_eq!(
|
|
current.plan_start_settlement(AlgorithmOutcome::Fulfilled),
|
|
StartSettlementPlan::ClearPendingAndPump
|
|
);
|
|
assert_eq!(
|
|
current.plan_start_settlement(AlgorithmOutcome::Rejected),
|
|
StartSettlementPlan::ClearPendingAndErrorBoth
|
|
);
|
|
assert_eq!(
|
|
current.plan_write_settlement(AlgorithmOutcome::Fulfilled, true),
|
|
WriteSettlementPlan::Fulfill {
|
|
finish_direct_write: true,
|
|
drain_pipe: true,
|
|
}
|
|
);
|
|
assert_eq!(
|
|
current.plan_write_settlement(AlgorithmOutcome::Rejected, false),
|
|
WriteSettlementPlan::Reject {
|
|
finish_direct_write: false,
|
|
error: current.plan_error(),
|
|
}
|
|
);
|
|
assert_eq!(
|
|
current.plan_writable_close_settlement(AlgorithmOutcome::Fulfilled),
|
|
WritableCloseSettlementPlan::MarkClosed
|
|
);
|
|
assert_eq!(
|
|
current.plan_writable_close_settlement(AlgorithmOutcome::Rejected),
|
|
WritableCloseSettlementPlan::ClearInFlightAndErrorWritable
|
|
);
|
|
assert_eq!(
|
|
current.plan_terminate().readable(),
|
|
ReadableTerminateAction::Close
|
|
);
|
|
}
|
|
}
|