add time machine for time manipulation mostly for testing/debugging
This commit is contained in:
parent
054c71e7f6
commit
3b5d5121a0
65
engine/examples/mock_time.rs
Normal file
65
engine/examples/mock_time.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use std::{
|
||||
process,
|
||||
sync::{Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use chrono::{prelude::*, TimeDelta};
|
||||
use clap::Parser;
|
||||
|
||||
// Struct to hold command-line arguments
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[clap(version, about = "run time machine")]
|
||||
struct Args {
|
||||
#[clap(short, long, help = "set time")]
|
||||
fake_time: Option<String>,
|
||||
}
|
||||
|
||||
// Thread-local storage for time offset when mocking the time
|
||||
lazy_static::lazy_static! {
|
||||
static ref DATE_TIME_DIFF: Arc<Mutex<Option<TimeDelta>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
// Set the mock time offset if `--fake-time` argument is provided
|
||||
pub fn set_mock_time(fake_time: &Option<String>) {
|
||||
if let Some(time) = fake_time {
|
||||
if let Ok(mock_time) = DateTime::parse_from_rfc3339(time) {
|
||||
let mock_time = mock_time.with_timezone(&Local);
|
||||
// Calculate the offset from the real current time
|
||||
let mut diff = DATE_TIME_DIFF.lock().unwrap();
|
||||
*diff = Some(Local::now() - mock_time);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Error: Invalid date format for --fake-time, use time with offset in: 2024-10-27T00:59:00+02:00"
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the current time, using either real or mock time based on `--fake-time`
|
||||
pub fn time_now() -> DateTime<Local> {
|
||||
let diff = DATE_TIME_DIFF.lock().unwrap();
|
||||
|
||||
if let Some(d) = &*diff {
|
||||
// If `--fake-time` is set, use the offset time
|
||||
Local::now() - *d
|
||||
} else {
|
||||
// Otherwise, use the real current time
|
||||
Local::now()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialize mock time if `--fake-time` is set
|
||||
set_mock_time(&args.fake_time);
|
||||
|
||||
loop {
|
||||
println!("Current time (or mocked time): {}", time_now());
|
||||
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@ pub async fn select_related_channels(
|
||||
) -> Result<Vec<Channel>, sqlx::Error> {
|
||||
let query = match user_id {
|
||||
Some(id) => format!(
|
||||
"SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.public, c.playlists, c.storage, c.last_date, c.time_shift FROM channels c
|
||||
"SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.public, c.playlists, c.storage, c.last_date, c.time_shift, c.timezone FROM channels c
|
||||
left join user_channels uc on uc.channel_id = c.id
|
||||
left join user u on u.id = uc.user_id
|
||||
WHERE u.id = {id} ORDER BY c.id ASC;"
|
||||
|
@ -74,6 +74,10 @@ pub struct Channel {
|
||||
pub storage: String,
|
||||
pub last_date: Option<String>,
|
||||
pub time_shift: f64,
|
||||
// not in use currently
|
||||
#[sqlx(default)]
|
||||
#[serde(default, skip_serializing)]
|
||||
pub timezone: Option<String>,
|
||||
|
||||
#[sqlx(default)]
|
||||
#[serde(default)]
|
||||
|
@ -31,6 +31,7 @@ use ffplayout::{
|
||||
config::get_config,
|
||||
logging::{init_logging, MailQueue},
|
||||
playlist::generate_playlist,
|
||||
time_machine::set_mock_time,
|
||||
},
|
||||
validator, ARGS,
|
||||
};
|
||||
@ -61,6 +62,8 @@ async fn main() -> std::io::Result<()> {
|
||||
exit(c);
|
||||
}
|
||||
|
||||
set_mock_time(&ARGS.fake_time);
|
||||
|
||||
init_globales(&pool).await;
|
||||
init_logging(mail_queues.clone())?;
|
||||
|
||||
|
@ -35,6 +35,7 @@ use crate::utils::{
|
||||
config::{OutputMode::*, PlayoutConfig, FFMPEG_IGNORE_ERRORS, FFMPEG_UNRECOVERABLE_ERRORS},
|
||||
errors::ProcessError,
|
||||
logging::Target,
|
||||
time_machine::time_now,
|
||||
};
|
||||
pub use json_serializer::{read_json, JsonPlaylist};
|
||||
|
||||
@ -1182,38 +1183,3 @@ pub fn custom_format<T: fmt::Display>(template: &str, args: &[T]) -> String {
|
||||
|
||||
filled_template
|
||||
}
|
||||
|
||||
/// Get system time, in non test/debug case.
|
||||
#[cfg(not(any(test, debug_assertions)))]
|
||||
pub fn time_now() -> DateTime<Local> {
|
||||
Local::now()
|
||||
}
|
||||
|
||||
/// Get mocked system time, in test/debug case.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub mod mock_time {
|
||||
use super::*;
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static DATE_TIME_DIFF: RefCell<Option<TimeDelta>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
pub fn time_now() -> DateTime<Local> {
|
||||
DATE_TIME_DIFF.with(|cell| match cell.borrow().as_ref().cloned() {
|
||||
Some(diff) => Local::now() - diff,
|
||||
None => Local::now(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_mock_time(date_time: &str) {
|
||||
if let Ok(d) = NaiveDateTime::parse_from_str(date_time, "%Y-%m-%dT%H:%M:%S") {
|
||||
let time = Local.from_local_datetime(&d).unwrap();
|
||||
|
||||
DATE_TIME_DIFF.with(|cell| *cell.borrow_mut() = Some(Local::now() - time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub use mock_time::time_now;
|
||||
|
@ -27,7 +27,7 @@ use crate::ARGS;
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::utils::db_path;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[derive(Parser, Debug, Default, Clone)]
|
||||
#[clap(version,
|
||||
about = "ffplayout - 24/7 broadcasting solution",
|
||||
long_about = Some("ffplayout - 24/7 broadcasting solution\n
|
||||
@ -140,6 +140,9 @@ pub struct Args {
|
||||
)]
|
||||
pub channels: Option<Vec<i32>>,
|
||||
|
||||
#[clap(long, hide = true, help = "set fake time (for debugging)")]
|
||||
pub fake_time: Option<String>,
|
||||
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
|
@ -23,7 +23,9 @@ use regex::Regex;
|
||||
use super::ARGS;
|
||||
|
||||
use crate::db::models::GlobalSettings;
|
||||
use crate::utils::{config::Mail, errors::ProcessError, round_to_nearest_ten};
|
||||
use crate::utils::{
|
||||
config::Mail, errors::ProcessError, round_to_nearest_ten, time_machine::time_now,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Target;
|
||||
@ -263,12 +265,18 @@ fn console_formatter(w: &mut dyn Write, now: &mut DeferredNow, record: &Record)
|
||||
};
|
||||
|
||||
if ARGS.log_timestamp {
|
||||
let time = if ARGS.fake_time.is_some() {
|
||||
time_now()
|
||||
} else {
|
||||
*now.now()
|
||||
};
|
||||
|
||||
write!(
|
||||
w,
|
||||
"{} {}",
|
||||
colorize_string(format!(
|
||||
"<bright black>[{}]</>",
|
||||
now.now().format("%Y-%m-%d %H:%M:%S%.6f")
|
||||
time.format("%Y-%m-%d %H:%M:%S%.6f")
|
||||
)),
|
||||
log_line
|
||||
)
|
||||
|
@ -32,6 +32,7 @@ pub mod logging;
|
||||
pub mod playlist;
|
||||
pub mod system;
|
||||
pub mod task_runner;
|
||||
pub mod time_machine;
|
||||
|
||||
use crate::db::models::GlobalSettings;
|
||||
use crate::player::utils::time_to_sec;
|
||||
|
43
engine/src/utils/time_machine.rs
Normal file
43
engine/src/utils/time_machine.rs
Normal file
@ -0,0 +1,43 @@
|
||||
/// These functions are made for testing purposes.
|
||||
/// It allows, with a hidden command line argument, to override the time in this program.
|
||||
/// It is like a time machine where you can fake the time and make the hole program think it is running in the future or past.
|
||||
use std::{
|
||||
process,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use chrono::{prelude::*, TimeDelta};
|
||||
|
||||
// Thread-local storage for time offset when mocking the time
|
||||
lazy_static::lazy_static! {
|
||||
static ref DATE_TIME_DIFF: Arc<Mutex<Option<TimeDelta>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
// Set the mock time offset if `--fake-time` argument is provided
|
||||
pub fn set_mock_time(fake_time: &Option<String>) {
|
||||
if let Some(time) = fake_time {
|
||||
if let Ok(mock_time) = DateTime::parse_from_rfc3339(time) {
|
||||
let mock_time = mock_time.with_timezone(&Local);
|
||||
// Calculate the offset from the real current time
|
||||
let mut diff = DATE_TIME_DIFF.lock().unwrap();
|
||||
*diff = Some(Local::now() - mock_time);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Error: Invalid date format for --fake-time, use time with offset in: 2024-10-27T00:59:00+02:00"
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the current time, using either real or mock time based on `--fake-time`
|
||||
pub fn time_now() -> DateTime<Local> {
|
||||
let diff = DATE_TIME_DIFF.lock().unwrap();
|
||||
if let Some(d) = &*diff {
|
||||
// If `--fake-time` is set, use the offset time
|
||||
Local::now() - *d
|
||||
} else {
|
||||
// Otherwise, use the real current time
|
||||
Local::now()
|
||||
}
|
||||
}
|
@ -29,13 +29,14 @@ CREATE TABLE
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
preview_url TEXT NOT NULL,
|
||||
extra_extensions TEXT NOT NULL DEFAULT 'jpg,jpeg,png',
|
||||
extra_extensions TEXT NOT NULL DEFAULT "jpg,jpeg,png",
|
||||
active INTEGER NOT NULL DEFAULT 0,
|
||||
public TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
|
||||
playlists TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists",
|
||||
storage TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media",
|
||||
last_date TEXT,
|
||||
time_shift REAL NOT NULL DEFAULT 0
|
||||
time_shift REAL NOT NULL DEFAULT 0,
|
||||
timezone TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE
|
||||
|
@ -9,10 +9,10 @@ use tokio::runtime::Runtime;
|
||||
|
||||
use ffplayout::db::handles;
|
||||
use ffplayout::player::output::player;
|
||||
use ffplayout::player::utils::*;
|
||||
use ffplayout::player::{controller::ChannelManager, input::playlist::gen_source, utils::Media};
|
||||
use ffplayout::utils::config::OutputMode::Null;
|
||||
use ffplayout::utils::config::{PlayoutConfig, ProcessMode::Playlist};
|
||||
use ffplayout::utils::time_machine::set_mock_time;
|
||||
use ffplayout::vec_strings;
|
||||
|
||||
async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
|
||||
@ -121,7 +121,7 @@ fn playlist_missing() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2023-02-07T23:59:45");
|
||||
set_mock_time(&Some("2023-02-07T23:59:45".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
@ -155,7 +155,7 @@ fn playlist_next_missing() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2023-02-09T23:59:45");
|
||||
set_mock_time(&Some("2023-02-09T23:59:45".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
@ -189,7 +189,7 @@ fn playlist_to_short() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2024-01-31T05:59:40");
|
||||
set_mock_time(&Some("2024-01-31T05:59:40".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
@ -223,7 +223,7 @@ fn playlist_init_after_list_end() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2024-01-31T05:59:47");
|
||||
set_mock_time(&Some("2024-01-31T05:59:47".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
@ -257,7 +257,7 @@ fn playlist_change_at_midnight() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2023-02-08T23:59:45");
|
||||
set_mock_time(&Some("2023-02-08T23:59:45".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
@ -291,7 +291,7 @@ fn playlist_change_before_midnight() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2023-02-08T23:59:30");
|
||||
set_mock_time(&Some("2023-02-08T23:59:30".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(35, manager_clone));
|
||||
|
||||
@ -325,7 +325,7 @@ fn playlist_change_at_six() {
|
||||
config.output.output_filter = None;
|
||||
config.output.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
mock_time::set_mock_time("2023-02-09T05:59:45");
|
||||
set_mock_time(&Some("2023-02-09T05:59:45".to_string()));
|
||||
|
||||
thread::spawn(move || timed_stop(28, manager_clone));
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(test)]
|
||||
use chrono::prelude::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use ffplayout::db::handles;
|
||||
use ffplayout::player::{controller::ChannelManager, utils::*};
|
||||
use ffplayout::utils::config::{PlayoutConfig, ProcessMode::Playlist};
|
||||
use ffplayout::utils::{
|
||||
config::{PlayoutConfig, ProcessMode::Playlist},
|
||||
time_machine::{set_mock_time, time_now},
|
||||
};
|
||||
|
||||
async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
|
||||
let pool = SqlitePoolOptions::new()
|
||||
@ -44,7 +45,7 @@ fn mock_date_time() {
|
||||
let date_obj = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%dT%H:%M:%S");
|
||||
let time = Local.from_local_datetime(&date_obj.unwrap()).unwrap();
|
||||
|
||||
mock_time::set_mock_time(time_str);
|
||||
set_mock_time(&Some(time_str.to_string()));
|
||||
|
||||
assert_eq!(
|
||||
time.format("%Y-%m-%dT%H:%M:%S.2f").to_string(),
|
||||
@ -54,7 +55,7 @@ fn mock_date_time() {
|
||||
|
||||
#[test]
|
||||
fn get_date_yesterday() {
|
||||
mock_time::set_mock_time("2022-05-20T05:59:24");
|
||||
set_mock_time(&Some("2022-05-20T05:59:24".to_string()));
|
||||
|
||||
let date = get_date(true, 21600.0, false);
|
||||
|
||||
@ -63,7 +64,7 @@ fn get_date_yesterday() {
|
||||
|
||||
#[test]
|
||||
fn get_date_tomorrow() {
|
||||
mock_time::set_mock_time("2022-05-20T23:59:58");
|
||||
set_mock_time(&Some("2022-05-20T23:59:58".to_string()));
|
||||
|
||||
let date = get_date(false, 0.0, true);
|
||||
|
||||
@ -79,7 +80,7 @@ fn test_delta() {
|
||||
config.playlist.day_start = "00:00:00".into();
|
||||
config.playlist.length = "24:00:00".into();
|
||||
|
||||
mock_time::set_mock_time("2022-05-09T23:59:59");
|
||||
set_mock_time(&Some("2022-05-09T23:59:59".to_string()));
|
||||
let (delta, _) = get_delta(&config, &86401.0);
|
||||
|
||||
assert!(delta < 2.0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user