#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GroupPolicy {
Sequential,
Parallel,
FirstSuccess,
}
impl GroupPolicy {
#[must_use]
pub fn description(&self) -> &'static str {
match self {
Self::Sequential => "Run tasks sequentially in order",
Self::Parallel => "Run all tasks in parallel",
Self::FirstSuccess => "Stop on first successful task",
}
}
#[must_use]
pub fn allows_concurrency(&self) -> bool {
matches!(self, Self::Parallel)
}
}
#[derive(Debug, Clone)]
pub struct Task {
pub name: String,
pub weight: u32,
}
impl Task {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
weight: 1,
}
}
#[must_use]
pub fn with_weight(name: impl Into<String>, weight: u32) -> Self {
Self {
name: name.into(),
weight,
}
}
}
#[derive(Debug, Clone)]
pub struct TaskGroup {
pub name: String,
pub policy: GroupPolicy,
tasks: Vec<Task>,
}
impl TaskGroup {
#[must_use]
pub fn new(name: impl Into<String>, policy: GroupPolicy) -> Self {
Self {
name: name.into(),
policy,
tasks: Vec::new(),
}
}
pub fn add_task(&mut self, task: Task) {
self.tasks.push(task);
}
#[must_use]
pub fn task_count(&self) -> usize {
self.tasks.len()
}
#[must_use]
pub fn tasks(&self) -> &[Task] {
&self.tasks
}
#[must_use]
pub fn total_weight(&self) -> u32 {
self.tasks.iter().map(|t| t.weight).sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskOutcome {
Success,
Failure(String),
Skipped,
}
impl TaskOutcome {
#[must_use]
pub fn is_success(&self) -> bool {
matches!(self, Self::Success)
}
#[must_use]
pub fn is_failure(&self) -> bool {
matches!(self, Self::Failure(_))
}
}
#[derive(Debug, Clone)]
pub struct GroupResult {
pub outcomes: Vec<(String, TaskOutcome)>,
}
impl GroupResult {
#[must_use]
pub fn new() -> Self {
Self {
outcomes: Vec::new(),
}
}
pub fn record(&mut self, task_name: impl Into<String>, outcome: TaskOutcome) {
self.outcomes.push((task_name.into(), outcome));
}
#[must_use]
pub fn all_success(&self) -> bool {
!self.outcomes.is_empty()
&& self
.outcomes
.iter()
.all(|(_, o)| matches!(o, TaskOutcome::Success))
}
#[must_use]
pub fn any_failure(&self) -> bool {
self.outcomes
.iter()
.any(|(_, o)| matches!(o, TaskOutcome::Failure(_)))
}
#[must_use]
pub fn success_count(&self) -> usize {
self.outcomes
.iter()
.filter(|(_, o)| matches!(o, TaskOutcome::Success))
.count()
}
#[must_use]
pub fn failure_count(&self) -> usize {
self.outcomes
.iter()
.filter(|(_, o)| matches!(o, TaskOutcome::Failure(_)))
.count()
}
#[must_use]
pub fn skipped_count(&self) -> usize {
self.outcomes
.iter()
.filter(|(_, o)| matches!(o, TaskOutcome::Skipped))
.count()
}
}
impl Default for GroupResult {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_group_policy_description_sequential() {
assert!(!GroupPolicy::Sequential.description().is_empty());
}
#[test]
fn test_group_policy_description_parallel() {
assert!(!GroupPolicy::Parallel.description().is_empty());
}
#[test]
fn test_group_policy_description_first_success() {
assert!(!GroupPolicy::FirstSuccess.description().is_empty());
}
#[test]
fn test_group_policy_allows_concurrency() {
assert!(GroupPolicy::Parallel.allows_concurrency());
assert!(!GroupPolicy::Sequential.allows_concurrency());
assert!(!GroupPolicy::FirstSuccess.allows_concurrency());
}
#[test]
fn test_task_default_weight() {
let t = Task::new("encode");
assert_eq!(t.weight, 1);
assert_eq!(t.name, "encode");
}
#[test]
fn test_task_with_weight() {
let t = Task::with_weight("upload", 5);
assert_eq!(t.weight, 5);
}
#[test]
fn test_task_group_add_and_count() {
let mut g = TaskGroup::new("pipeline", GroupPolicy::Sequential);
assert_eq!(g.task_count(), 0);
assert!(g.is_empty());
g.add_task(Task::new("step1"));
g.add_task(Task::new("step2"));
assert_eq!(g.task_count(), 2);
assert!(!g.is_empty());
}
#[test]
fn test_task_group_total_weight() {
let mut g = TaskGroup::new("grp", GroupPolicy::Parallel);
g.add_task(Task::with_weight("a", 3));
g.add_task(Task::with_weight("b", 7));
assert_eq!(g.total_weight(), 10);
}
#[test]
fn test_task_group_tasks_slice() {
let mut g = TaskGroup::new("grp", GroupPolicy::Parallel);
g.add_task(Task::new("x"));
assert_eq!(g.tasks().len(), 1);
assert_eq!(g.tasks()[0].name, "x");
}
#[test]
fn test_task_outcome_is_success() {
assert!(TaskOutcome::Success.is_success());
assert!(!TaskOutcome::Failure("err".to_string()).is_success());
assert!(!TaskOutcome::Skipped.is_success());
}
#[test]
fn test_task_outcome_is_failure() {
assert!(TaskOutcome::Failure("oops".to_string()).is_failure());
assert!(!TaskOutcome::Success.is_failure());
}
#[test]
fn test_group_result_all_success_true() {
let mut r = GroupResult::new();
r.record("t1", TaskOutcome::Success);
r.record("t2", TaskOutcome::Success);
assert!(r.all_success());
}
#[test]
fn test_group_result_all_success_false_on_failure() {
let mut r = GroupResult::new();
r.record("t1", TaskOutcome::Success);
r.record("t2", TaskOutcome::Failure("fail".to_string()));
assert!(!r.all_success());
}
#[test]
fn test_group_result_all_success_false_when_empty() {
let r = GroupResult::new();
assert!(!r.all_success());
}
#[test]
fn test_group_result_counts() {
let mut r = GroupResult::new();
r.record("a", TaskOutcome::Success);
r.record("b", TaskOutcome::Failure("e".to_string()));
r.record("c", TaskOutcome::Skipped);
assert_eq!(r.success_count(), 1);
assert_eq!(r.failure_count(), 1);
assert_eq!(r.skipped_count(), 1);
assert!(r.any_failure());
}
#[test]
fn test_group_result_default() {
let r = GroupResult::default();
assert!(r.outcomes.is_empty());
}
}