1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::fmt;
6use std::fs;
7use std::io::BufRead;
8use std::path::Path;
9use std::sync::Arc;
10use std::sync::atomic::AtomicU8;
11use std::sync::atomic::Ordering;
12
13use anyhow::Context;
14use anyhow::Result;
15use anyhow::bail;
16use cloud_copy::TransferEvent;
17use crankshaft::events::Event as CrankshaftEvent;
18use indexmap::IndexMap;
19use itertools::Itertools;
20use num_enum::IntoPrimitive;
21use rev_buf_reader::RevBufReader;
22use tokio::sync::broadcast;
23use tokio_util::sync::CancellationToken;
24use tracing::error;
25use wdl_analysis::Document;
26use wdl_analysis::document::Task;
27use wdl_analysis::types::Type;
28use wdl_ast::Diagnostic;
29use wdl_ast::Span;
30use wdl_ast::SupportedVersion;
31use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES;
32use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES_ALIAS;
33
34use crate::CompoundValue;
35use crate::Outputs;
36use crate::PrimitiveValue;
37use crate::Value;
38use crate::backend::TaskExecutionResult;
39use crate::cache::Hashable;
40use crate::config::FailureMode;
41use crate::http::Location;
42use crate::http::Transferer;
43use crate::path;
44use crate::path::EvaluationPath;
45use crate::stdlib::download_file;
46
47pub mod trie;
48pub mod v1;
49
50const MAX_STDERR_LINES: usize = 10;
52
53const ROOT_NAME: &str = ".root";
57
58const CANCELLATION_STATE_NOT_CANCELED: u8 = 0;
60
61const CANCELLATION_STATE_WAITING: u8 = 1;
66
67const CANCELLATION_STATE_CANCELING: u8 = 2;
71
72const CANCELLATION_STATE_ERROR: u8 = 4;
77
78const CANCELLATION_STATE_MASK: u8 = 0x3;
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum CancellationContextState {
84 NotCanceled,
86 Waiting,
89 Canceling,
92}
93
94impl CancellationContextState {
95 fn get(state: &Arc<AtomicU8>) -> Self {
97 match state.load(Ordering::SeqCst) & CANCELLATION_STATE_MASK {
98 CANCELLATION_STATE_NOT_CANCELED => Self::NotCanceled,
99 CANCELLATION_STATE_WAITING => Self::Waiting,
100 CANCELLATION_STATE_CANCELING => Self::Canceling,
101 _ => unreachable!("unexpected cancellation context state"),
102 }
103 }
104
105 fn update(mode: FailureMode, error: bool, state: &Arc<AtomicU8>) -> Option<Self> {
110 let previous_state = state
112 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
113 if error && state != CANCELLATION_STATE_NOT_CANCELED {
115 return None;
116 }
117
118 let mut new_state = match state & CANCELLATION_STATE_MASK {
120 CANCELLATION_STATE_NOT_CANCELED => match mode {
121 FailureMode::Slow => CANCELLATION_STATE_WAITING,
122 FailureMode::Fast => CANCELLATION_STATE_CANCELING,
123 },
124 CANCELLATION_STATE_WAITING => CANCELLATION_STATE_CANCELING,
125 CANCELLATION_STATE_CANCELING => CANCELLATION_STATE_CANCELING,
126 _ => unreachable!("unexpected cancellation context state"),
127 };
128
129 if error {
131 new_state |= CANCELLATION_STATE_ERROR;
132 }
133
134 Some(new_state | (state & CANCELLATION_STATE_ERROR))
136 })
137 .ok()?;
138
139 match previous_state & CANCELLATION_STATE_MASK {
140 CANCELLATION_STATE_NOT_CANCELED => match mode {
141 FailureMode::Slow => Some(Self::Waiting),
142 FailureMode::Fast => Some(Self::Canceling),
143 },
144 CANCELLATION_STATE_WAITING => Some(Self::Canceling),
145 CANCELLATION_STATE_CANCELING => Some(Self::Canceling),
146 _ => unreachable!("unexpected cancellation context state"),
147 }
148 }
149}
150
151#[derive(Clone)]
155pub struct CancellationContext {
156 mode: FailureMode,
158 state: Arc<AtomicU8>,
160 token: CancellationToken,
162}
163
164impl CancellationContext {
165 pub fn new(mode: FailureMode) -> Self {
175 Self {
176 mode,
177 state: Arc::new(CANCELLATION_STATE_NOT_CANCELED.into()),
178 token: CancellationToken::new(),
179 }
180 }
181
182 pub fn state(&self) -> CancellationContextState {
184 CancellationContextState::get(&self.state)
185 }
186
187 #[must_use]
195 pub fn cancel(&self) -> CancellationContextState {
196 let state =
197 CancellationContextState::update(self.mode, false, &self.state).expect("should update");
198 assert!(
199 state != CancellationContextState::NotCanceled,
200 "should be canceled"
201 );
202
203 if state == CancellationContextState::Canceling {
204 self.token.cancel();
205 }
206
207 state
208 }
209
210 pub fn token(&self) -> CancellationToken {
219 self.token.clone()
220 }
221
222 pub(crate) fn user_canceled(&self) -> bool {
224 let state = self.state.load(Ordering::SeqCst);
225 state != CANCELLATION_STATE_NOT_CANCELED && (state & CANCELLATION_STATE_ERROR == 0)
226 }
227
228 pub(crate) fn error(&self, error: &EvaluationError) {
235 if let Some(state) = CancellationContextState::update(self.mode, true, &self.state) {
236 let message: Cow<'_, str> = match error {
237 EvaluationError::Canceled => "evaluation was canceled".into(),
238 EvaluationError::Source(e) => e.diagnostic.message().into(),
239 EvaluationError::Other(e) => format!("{e:#}").into(),
240 };
241
242 match state {
243 CancellationContextState::NotCanceled => unreachable!("should be canceled"),
244 CancellationContextState::Waiting => {
245 error!(
246 "an evaluation error occurred: waiting for any executing tasks to \
247 complete: {message}"
248 );
249 }
250 CancellationContextState::Canceling => {
251 self.token.cancel();
252
253 error!(
254 "an evaluation error occurred: waiting for any executing tasks to cancel: \
255 {message}"
256 );
257 }
258 }
259 }
260 }
261}
262
263impl Default for CancellationContext {
264 fn default() -> Self {
265 Self::new(FailureMode::Slow)
266 }
267}
268
269#[derive(Debug, Clone)]
271pub enum EngineEvent {
272 ReusedCachedExecutionResult {
274 id: String,
276 },
277}
278
279#[derive(Debug, Clone, Default)]
281pub struct Events {
282 engine: Option<broadcast::Sender<EngineEvent>>,
286 crankshaft: Option<broadcast::Sender<CrankshaftEvent>>,
290 transfer: Option<broadcast::Sender<TransferEvent>>,
294}
295
296impl Events {
297 pub fn new(capacity: usize) -> Self {
299 Self {
300 engine: Some(broadcast::Sender::new(capacity)),
301 crankshaft: Some(broadcast::Sender::new(capacity)),
302 transfer: Some(broadcast::Sender::new(capacity)),
303 }
304 }
305
306 pub fn disabled() -> Self {
308 Self::default()
309 }
310
311 pub fn subscribe_engine(&self) -> Option<broadcast::Receiver<EngineEvent>> {
315 self.engine.as_ref().map(|s| s.subscribe())
316 }
317
318 pub fn subscribe_crankshaft(&self) -> Option<broadcast::Receiver<CrankshaftEvent>> {
322 self.crankshaft.as_ref().map(|s| s.subscribe())
323 }
324
325 pub fn subscribe_transfer(&self) -> Option<broadcast::Receiver<TransferEvent>> {
329 self.transfer.as_ref().map(|s| s.subscribe())
330 }
331
332 pub(crate) fn engine(&self) -> &Option<broadcast::Sender<EngineEvent>> {
334 &self.engine
335 }
336
337 pub(crate) fn crankshaft(&self) -> &Option<broadcast::Sender<CrankshaftEvent>> {
339 &self.crankshaft
340 }
341
342 pub(crate) fn transfer(&self) -> &Option<broadcast::Sender<TransferEvent>> {
344 &self.transfer
345 }
346}
347
348#[derive(Debug, Clone)]
350pub struct CallLocation {
351 pub document: Document,
353 pub span: Span,
355}
356
357#[derive(Debug)]
359pub struct SourceError {
360 pub document: Document,
362 pub diagnostic: Diagnostic,
364 pub backtrace: Vec<CallLocation>,
371}
372
373#[derive(Debug)]
375pub enum EvaluationError {
376 Canceled,
378 Source(Box<SourceError>),
380 Other(anyhow::Error),
382}
383
384impl EvaluationError {
385 pub fn new(document: Document, diagnostic: Diagnostic) -> Self {
387 Self::Source(Box::new(SourceError {
388 document,
389 diagnostic,
390 backtrace: Default::default(),
391 }))
392 }
393
394 #[allow(clippy::inherent_to_string)]
396 pub fn to_string(&self) -> String {
397 use std::collections::HashMap;
398
399 use codespan_reporting::diagnostic::Label;
400 use codespan_reporting::diagnostic::LabelStyle;
401 use codespan_reporting::files::SimpleFiles;
402 use codespan_reporting::term::Config;
403 use codespan_reporting::term::termcolor::Buffer;
404 use codespan_reporting::term::{self};
405 use wdl_ast::AstNode;
406
407 match self {
408 Self::Canceled => "evaluation was canceled".to_string(),
409 Self::Source(e) => {
410 let mut files = SimpleFiles::new();
411 let mut map = HashMap::new();
412
413 let file_id = files.add(e.document.path(), e.document.root().text().to_string());
414
415 let diagnostic =
416 e.diagnostic
417 .to_codespan(file_id)
418 .with_labels_iter(e.backtrace.iter().map(|l| {
419 let id = l.document.id();
420 let file_id = *map.entry(id).or_insert_with(|| {
421 files.add(l.document.path(), l.document.root().text().to_string())
422 });
423
424 Label {
425 style: LabelStyle::Secondary,
426 file_id,
427 range: l.span.start()..l.span.end(),
428 message: "called from this location".into(),
429 }
430 }));
431
432 let mut buffer = Buffer::no_color();
433 term::emit(&mut buffer, &Config::default(), &files, &diagnostic)
434 .expect("failed to emit diagnostic");
435
436 String::from_utf8(buffer.into_inner()).expect("should be UTF-8")
437 }
438 Self::Other(e) => format!("{e:?}"),
439 }
440 }
441}
442
443impl From<anyhow::Error> for EvaluationError {
444 fn from(e: anyhow::Error) -> Self {
445 Self::Other(e)
446 }
447}
448
449pub type EvaluationResult<T> = Result<T, EvaluationError>;
451
452#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
457pub struct HostPath(pub(crate) Arc<String>);
458
459impl HostPath {
460 pub fn new(path: impl Into<String>) -> Self {
462 Self(Arc::new(path.into()))
463 }
464
465 pub fn as_str(&self) -> &str {
467 &self.0
468 }
469
470 pub fn expand(&self, base_dir: &EvaluationPath) -> Result<Self> {
474 let shell_expanded = shellexpand::full(self.as_str()).with_context(|| {
476 format!("failed to shell-expand path `{path}`", path = self.as_str())
477 })?;
478
479 if path::is_supported_url(&shell_expanded) {
481 Ok(Self::new(shell_expanded))
482 } else {
483 Ok(Self::new(
485 base_dir.join(&shell_expanded)?.display().to_string(),
486 ))
487 }
488 }
489
490 pub fn is_relative(&self) -> bool {
492 !path::is_supported_url(&self.0) && Path::new(self.0.as_str()).is_relative()
493 }
494}
495
496impl fmt::Display for HostPath {
497 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498 self.0.fmt(f)
499 }
500}
501
502impl From<Arc<String>> for HostPath {
503 fn from(path: Arc<String>) -> Self {
504 Self(path)
505 }
506}
507
508impl From<HostPath> for Arc<String> {
509 fn from(path: HostPath) -> Self {
510 path.0
511 }
512}
513
514impl From<String> for HostPath {
515 fn from(s: String) -> Self {
516 Arc::new(s).into()
517 }
518}
519
520impl<'a> From<&'a str> for HostPath {
521 fn from(s: &'a str) -> Self {
522 s.to_string().into()
523 }
524}
525
526impl From<url::Url> for HostPath {
527 fn from(url: url::Url) -> Self {
528 url.as_str().into()
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
539pub struct GuestPath(pub(crate) Arc<String>);
540
541impl GuestPath {
542 pub fn new(path: impl Into<String>) -> Self {
544 Self(Arc::new(path.into()))
545 }
546
547 pub fn as_str(&self) -> &str {
549 &self.0
550 }
551}
552
553impl fmt::Display for GuestPath {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 self.0.fmt(f)
556 }
557}
558
559impl From<Arc<String>> for GuestPath {
560 fn from(path: Arc<String>) -> Self {
561 Self(path)
562 }
563}
564
565impl From<GuestPath> for Arc<String> {
566 fn from(path: GuestPath) -> Self {
567 path.0
568 }
569}
570
571pub trait EvaluationContext: Send + Sync {
573 fn version(&self) -> SupportedVersion;
575
576 fn resolve_name(&self, name: &str, span: Span) -> Result<Value, Diagnostic>;
578
579 fn resolve_type_name(&self, name: &str, span: Span) -> Result<Type, Diagnostic>;
581
582 fn base_dir(&self) -> &EvaluationPath;
591
592 fn temp_dir(&self) -> &Path;
594
595 fn stdout(&self) -> Option<&Value> {
599 None
600 }
601
602 fn stderr(&self) -> Option<&Value> {
606 None
607 }
608
609 fn task(&self) -> Option<&Task> {
613 None
614 }
615
616 fn transferer(&self) -> &dyn Transferer;
618
619 fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
624 let _ = path;
625 None
626 }
627
628 fn host_path(&self, path: &GuestPath) -> Option<HostPath> {
633 let _ = path;
634 None
635 }
636
637 fn notify_file_created(&mut self, path: &HostPath) -> Result<()> {
642 let _ = path;
643 Ok(())
644 }
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
649pub struct ScopeIndex(usize);
650
651impl ScopeIndex {
652 pub const fn new(index: usize) -> Self {
654 Self(index)
655 }
656}
657
658impl From<usize> for ScopeIndex {
659 fn from(index: usize) -> Self {
660 Self(index)
661 }
662}
663
664impl From<ScopeIndex> for usize {
665 fn from(index: ScopeIndex) -> Self {
666 index.0
667 }
668}
669
670#[derive(Default, Debug)]
672pub struct Scope {
673 parent: Option<ScopeIndex>,
677 names: IndexMap<String, Value>,
679}
680
681impl Scope {
682 pub fn new(parent: ScopeIndex) -> Self {
684 Self {
685 parent: Some(parent),
686 names: Default::default(),
687 }
688 }
689
690 pub fn insert(&mut self, name: impl Into<String>, value: impl Into<Value>) {
692 let prev = self.names.insert(name.into(), value.into());
693 assert!(prev.is_none(), "conflicting name in scope");
694 }
695
696 pub fn local(&self) -> impl Iterator<Item = (&str, &Value)> + use<'_> {
698 self.names.iter().map(|(k, v)| (k.as_str(), v))
699 }
700
701 pub(crate) fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
703 self.names.get_mut(name)
704 }
705
706 pub(crate) fn clear(&mut self) {
708 self.parent = None;
709 self.names.clear();
710 }
711
712 pub(crate) fn set_parent(&mut self, parent: ScopeIndex) {
714 self.parent = Some(parent);
715 }
716}
717
718impl From<Scope> for IndexMap<String, Value> {
719 fn from(scope: Scope) -> Self {
720 scope.names
721 }
722}
723
724#[derive(Debug, Clone, Copy)]
726pub struct ScopeRef<'a> {
727 scopes: &'a [Scope],
729 index: ScopeIndex,
731}
732
733impl<'a> ScopeRef<'a> {
734 pub fn new(scopes: &'a [Scope], index: impl Into<ScopeIndex>) -> Self {
736 Self {
737 scopes,
738 index: index.into(),
739 }
740 }
741
742 pub fn parent(&self) -> Option<Self> {
746 self.scopes[self.index.0].parent.map(|p| Self {
747 scopes: self.scopes,
748 index: p,
749 })
750 }
751
752 pub fn names(&self) -> impl Iterator<Item = (&str, &Value)> + use<'_> {
754 self.scopes[self.index.0]
755 .names
756 .iter()
757 .map(|(n, name)| (n.as_str(), name))
758 }
759
760 pub fn for_each(&self, mut cb: impl FnMut(&str, &Value) -> Result<()>) -> Result<()> {
765 let mut current = Some(self.index);
766
767 while let Some(index) = current {
768 for (n, v) in self.scopes[index.0].local() {
769 cb(n, v)?;
770 }
771
772 current = self.scopes[index.0].parent;
773 }
774
775 Ok(())
776 }
777
778 pub fn local(&self, name: &str) -> Option<&Value> {
782 self.scopes[self.index.0].names.get(name)
783 }
784
785 pub fn lookup(&self, name: &str) -> Option<&Value> {
789 let mut current = Some(self.index);
790
791 while let Some(index) = current {
792 if let Some(name) = self.scopes[index.0].names.get(name) {
793 return Some(name);
794 }
795
796 current = self.scopes[index.0].parent;
797 }
798
799 None
800 }
801}
802
803#[derive(Debug)]
805pub struct EvaluatedTask {
806 cached: bool,
808 result: TaskExecutionResult,
810 outputs: EvaluationResult<Outputs>,
818}
819
820impl EvaluatedTask {
821 fn new(cached: bool, result: TaskExecutionResult) -> Self {
823 Self {
824 cached,
825 result,
826 outputs: Ok(Default::default()),
827 }
828 }
829
830 pub fn cached(&self) -> bool {
833 self.cached
834 }
835
836 pub fn exit_code(&self) -> i32 {
838 self.result.exit_code
839 }
840
841 pub fn work_dir(&self) -> &EvaluationPath {
843 &self.result.work_dir
844 }
845
846 pub fn stdout(&self) -> &Value {
848 &self.result.stdout
849 }
850
851 pub fn stderr(&self) -> &Value {
853 &self.result.stderr
854 }
855
856 pub fn into_result(self) -> EvaluationResult<Outputs> {
862 self.outputs
863 }
864
865 async fn handle_exit(
869 &self,
870 requirements: &HashMap<String, Value>,
871 transferer: &dyn Transferer,
872 ) -> anyhow::Result<()> {
873 let mut error = true;
874 if let Some(return_codes) = requirements
875 .get(TASK_REQUIREMENT_RETURN_CODES)
876 .or_else(|| requirements.get(TASK_REQUIREMENT_RETURN_CODES_ALIAS))
877 {
878 match return_codes {
879 Value::Primitive(PrimitiveValue::String(s)) if s.as_ref() == "*" => {
880 error = false;
881 }
882 Value::Primitive(PrimitiveValue::String(s)) => {
883 bail!(
884 "invalid return code value `{s}`: only `*` is accepted when the return \
885 code is specified as a string"
886 );
887 }
888 Value::Primitive(PrimitiveValue::Integer(ok)) => {
889 if self.result.exit_code == i32::try_from(*ok).unwrap_or_default() {
890 error = false;
891 }
892 }
893 Value::Compound(CompoundValue::Array(codes)) => {
894 error = !codes.as_slice().iter().any(|v| {
895 v.as_integer()
896 .map(|i| i32::try_from(i).unwrap_or_default() == self.result.exit_code)
897 .unwrap_or(false)
898 });
899 }
900 _ => unreachable!("unexpected return codes value"),
901 }
902 } else {
903 error = self.result.exit_code != 0;
904 }
905
906 if error {
907 let stderr = download_file(
910 transferer,
911 self.work_dir(),
912 self.stderr().as_file().unwrap(),
913 )
914 .await
915 .ok()
916 .and_then(|l| {
917 fs::File::open(l).ok().map(|f| {
918 let reader = RevBufReader::new(f);
920 let lines: Vec<_> = reader
921 .lines()
922 .take(MAX_STDERR_LINES)
923 .map_while(|l| l.ok())
924 .collect();
925
926 lines
928 .iter()
929 .rev()
930 .format_with("\n", |l, f| f(&format_args!(" {l}")))
931 .to_string()
932 })
933 })
934 .unwrap_or_default();
935
936 bail!(
938 "process terminated with exit code {code}: see `{stdout_path}` and \
939 `{stderr_path}` for task output{header}{stderr}{trailer}",
940 code = self.result.exit_code,
941 stdout_path = self.stdout().as_file().expect("must be file"),
942 stderr_path = self.stderr().as_file().expect("must be file"),
943 header = if stderr.is_empty() {
944 Cow::Borrowed("")
945 } else {
946 format!("\n\ntask stderr output (last {MAX_STDERR_LINES} lines):\n\n").into()
947 },
948 trailer = if stderr.is_empty() { "" } else { "\n" }
949 );
950 }
951
952 Ok(())
953 }
954}
955
956#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive)]
958#[repr(u8)]
959pub enum ContentKind {
960 File,
962 Directory,
964}
965
966impl Hashable for ContentKind {
967 fn hash(&self, hasher: &mut blake3::Hasher) {
968 hasher.update(&[(*self).into()]);
969 }
970}
971
972impl From<ContentKind> for crankshaft::engine::task::input::Type {
973 fn from(value: ContentKind) -> Self {
974 match value {
975 ContentKind::File => Self::File,
976 ContentKind::Directory => Self::Directory,
977 }
978 }
979}
980
981#[derive(Debug, Clone)]
983pub struct Input {
984 kind: ContentKind,
986 path: EvaluationPath,
988 guest_path: Option<GuestPath>,
992 location: Option<Location>,
996}
997
998impl Input {
999 pub(crate) fn new(
1001 kind: ContentKind,
1002 path: EvaluationPath,
1003 guest_path: Option<GuestPath>,
1004 ) -> Self {
1005 Self {
1006 kind,
1007 path,
1008 guest_path,
1009 location: None,
1010 }
1011 }
1012
1013 pub fn kind(&self) -> ContentKind {
1015 self.kind
1016 }
1017
1018 pub fn path(&self) -> &EvaluationPath {
1022 &self.path
1023 }
1024
1025 pub fn guest_path(&self) -> Option<&GuestPath> {
1029 self.guest_path.as_ref()
1030 }
1031
1032 pub fn local_path(&self) -> Option<&Path> {
1036 self.location.as_deref().or_else(|| self.path.as_local())
1037 }
1038
1039 pub fn set_location(&mut self, location: Location) {
1043 self.location = Some(location);
1044 }
1045}
1046
1047#[cfg(test)]
1048mod test {
1049 use super::*;
1050
1051 #[test]
1052 fn cancellation_slow() {
1053 let context = CancellationContext::new(FailureMode::Slow);
1054 assert_eq!(context.state(), CancellationContextState::NotCanceled);
1055
1056 assert_eq!(context.cancel(), CancellationContextState::Waiting);
1058 assert_eq!(context.state(), CancellationContextState::Waiting);
1059 assert!(context.user_canceled());
1060 assert!(!context.token.is_cancelled());
1061
1062 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1064 assert_eq!(context.state(), CancellationContextState::Canceling);
1065 assert!(context.user_canceled());
1066 assert!(context.token.is_cancelled());
1067
1068 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1070 assert_eq!(context.state(), CancellationContextState::Canceling);
1071 assert!(context.user_canceled());
1072 assert!(context.token.is_cancelled());
1073 }
1074
1075 #[test]
1076 fn cancellation_fast() {
1077 let context = CancellationContext::new(FailureMode::Fast);
1078 assert_eq!(context.state(), CancellationContextState::NotCanceled);
1079
1080 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1082 assert_eq!(context.state(), CancellationContextState::Canceling);
1083 assert!(context.user_canceled());
1084 assert!(context.token.is_cancelled());
1085
1086 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1088 assert_eq!(context.state(), CancellationContextState::Canceling);
1089 assert!(context.user_canceled());
1090 assert!(context.token.is_cancelled());
1091 }
1092
1093 #[test]
1094 fn cancellation_error_slow() {
1095 let context = CancellationContext::new(FailureMode::Slow);
1096 assert_eq!(context.state(), CancellationContextState::NotCanceled);
1097
1098 context.error(&EvaluationError::Canceled);
1100 assert_eq!(context.state(), CancellationContextState::Waiting);
1101 assert!(!context.user_canceled());
1102 assert!(!context.token.is_cancelled());
1103
1104 context.error(&EvaluationError::Canceled);
1106 assert_eq!(context.state(), CancellationContextState::Waiting);
1107 assert!(!context.user_canceled());
1108 assert!(!context.token.is_cancelled());
1109
1110 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1112 assert_eq!(context.state(), CancellationContextState::Canceling);
1113 assert!(!context.user_canceled());
1114 assert!(context.token.is_cancelled());
1115 }
1116
1117 #[test]
1118 fn cancellation_error_fast() {
1119 let context = CancellationContext::new(FailureMode::Fast);
1120 assert_eq!(context.state(), CancellationContextState::NotCanceled);
1121
1122 context.error(&EvaluationError::Canceled);
1124 assert_eq!(context.state(), CancellationContextState::Canceling);
1125 assert!(!context.user_canceled());
1126 assert!(context.token.is_cancelled());
1127
1128 context.error(&EvaluationError::Canceled);
1130 assert_eq!(context.state(), CancellationContextState::Canceling);
1131 assert!(!context.user_canceled());
1132 assert!(context.token.is_cancelled());
1133
1134 assert_eq!(context.cancel(), CancellationContextState::Canceling);
1136 assert_eq!(context.state(), CancellationContextState::Canceling);
1137 assert!(!context.user_canceled());
1138 assert!(context.token.is_cancelled());
1139 }
1140}