use std::{fmt, sync::mpsc::Sender, thread};
use serde::{Deserialize, Serialize};
pub mod event;
pub mod reporter;
pub trait Task {
fn run(&self, ch: Sender<FinishedTask>);
fn info(&self) -> TaskInfo;
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct TaskInfo {
tid: TaskId,
tname: TaskName,
}
impl TaskInfo {
pub fn new(tid: TaskId, tname: TaskName) -> Self {
Self { tid, tname }
}
pub fn tid(&self) -> TaskId {
self.tid
}
pub fn tname(&self) -> TaskName {
self.tname.clone()
}
}
impl From<TaskInfo> for String {
fn from(info: TaskInfo) -> Self {
format!("{}", info)
}
}
impl std::fmt::Display for TaskInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "tname:{} tid:{}", self.tname(), self.tid())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct TaskId(usize);
impl From<usize> for TaskId {
fn from(u: usize) -> Self {
TaskId(u)
}
}
impl TaskId {
pub fn new(id: usize) -> Self {
TaskId(id)
}
}
impl std::fmt::Display for TaskId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct TaskName(String);
impl From<String> for TaskName {
fn from(s: String) -> Self {
TaskName(s)
}
}
impl std::fmt::Display for TaskName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<TaskName> for String {
fn from(task_name: TaskName) -> Self {
task_name.0
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum TaskStatus {
Finished,
Waiting,
Failed(String),
Bug(String),
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TaskStatus::Finished => {
write!(f, "{}", "finished")
}
TaskStatus::Waiting => {
write!(f, "{}", "waiting")
}
TaskStatus::Failed(reason) => {
write!(f, "{}:{}", "failed", reason)
}
TaskStatus::Bug(reason) => {
write!(f, "{}:{}", "bug", reason)
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FinishedTask {
tinfo: TaskInfo,
stdout: Vec<u8>,
stderr: Vec<u8>,
status: TaskStatus,
}
impl PartialEq for FinishedTask {
fn eq(&self, other: &Self) -> bool {
self.tinfo == other.tinfo
&& self.stdout == other.stdout
&& self.stderr == other.stderr
&& self.status == other.status
}
}
impl std::fmt::Display for FinishedTask {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} finished\nstdout:\n{}\nstderr:\n{}\nstatus:{}",
self.tinfo(),
String::from_utf8_lossy(&self.stdout()),
String::from_utf8_lossy(&self.stderr()),
self.status
)
}
}
impl FinishedTask {
pub fn new(tinfo: TaskInfo, stdout: Vec<u8>, stderr: Vec<u8>, status: TaskStatus) -> Self {
Self {
tinfo,
stdout,
stderr,
status,
}
}
pub fn tinfo(&self) -> TaskInfo {
self.tinfo.clone()
}
pub fn status(&self) -> TaskStatus {
self.status.clone()
}
pub fn stdout(&self) -> Vec<u8> {
self.stdout.clone()
}
pub fn stderr(&self) -> Vec<u8> {
self.stderr.clone()
}
pub fn find_bug(&mut self, info: String) {
self.status = TaskStatus::Bug(info)
}
pub fn short_msg(&self) -> String {
format!("{} finished, status:{}\n", self.tinfo(), self.status)
}
pub fn details(&self) -> String {
format!(
"\n{} finished\nstdout:{}\nstderr:{}\nstatus:{}\n",
self.tinfo(),
String::from_utf8_lossy(&self.stdout()),
String::from_utf8_lossy(&self.stderr()),
self.status
)
}
}
pub(crate) struct RunningTask {
pub(crate) join_handle: Option<thread::JoinHandle<()>>,
}
impl RunningTask {
pub(crate) fn join(self, task: &mut FinishedTask) {
if let Some(join_handle) = self.join_handle {
if join_handle.join().is_err() {
if let TaskStatus::Finished = task.status() {
task.find_bug(format!(
"Exception occurs after task '{}' reporting success",
task.tinfo()
));
}
}
}
}
}
#[allow(unused)]
mod test {
use std::{
sync::mpsc::{channel, Sender},
thread,
};
use crate::task::RunningTask;
use super::{FinishedTask, Task, TaskInfo, TaskStatus};
struct MyTask {
id: usize,
name: String,
}
impl MyTask {
pub fn new(id: usize, name: String) -> Self {
Self { id, name }
}
}
impl Task for MyTask {
fn run(&self, ch: Sender<super::FinishedTask>) {
ch.send(FinishedTask::new(
TaskInfo::new(self.id.into(), self.name.clone().into()),
vec![],
vec![],
TaskStatus::Finished,
))
.unwrap();
}
fn info(&self) -> TaskInfo {
TaskInfo::new(self.id.into(), self.name.clone().into())
}
}
#[test]
fn test_get_finish_task_from_mytask() {
let (tx, rx) = channel::<FinishedTask>();
let my_task = MyTask::new(0, "MyTask 0".to_string());
my_task.run(tx);
match rx.recv() {
Ok(res) => {
assert_eq!(res.tinfo().tid(), 0.into());
assert_eq!(res.tinfo().tname(), "MyTask 0".to_string().into());
assert_eq!(res.status, TaskStatus::Finished);
}
Err(_) => panic!("unreachable code"),
}
}
#[test]
fn test_running_task_join() {
let (tx, rx) = channel::<FinishedTask>();
let my_task = MyTask::new(0, "MyTask 0".to_string());
let running_task = RunningTask {
join_handle: Some(thread::spawn(move || {
my_task.run(tx);
})),
};
let mut res = rx.recv().unwrap();
running_task.join(&mut res);
assert_eq!(res.status(), TaskStatus::Finished);
}
}