Mercurial > hg
changeset 50214:8e0d823ef182 stable
testing: introduce util function to synchronize concurrent commands on files
This is an extension of mechanisms that the tests have been using for a while.
To be able to also control the execution in Rust, we introduce utility to
perform such `wait_on_file` logic based on some configuration value.
This will be used in the tests introduced in the next changesets.
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Tue, 28 Feb 2023 00:01:41 +0100 |
parents | f5e4248e5bce |
children | ae61851e6fe2 |
files | mercurial/testing/__init__.py rust/hg-core/src/utils.rs rust/hg-core/src/utils/debug.rs |
diffstat | 3 files changed, 97 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/testing/__init__.py Tue Feb 28 00:04:32 2023 +0100 +++ b/mercurial/testing/__init__.py Tue Feb 28 00:01:41 2023 +0100 @@ -9,6 +9,21 @@ environ = getattr(os, 'environ') +def wait_on_cfg(ui, cfg, timeout=10): + """synchronize on the `cfg` config path + + Use this to synchronize commands during race tests. + """ + full_config = b'sync.' + cfg + wait_config = full_config + b'-timeout' + sync_path = ui.config(b'devel', full_config) + if sync_path is not None: + timeout = ui.config(b'devel', wait_config) + ready_path = sync_path + b'.waiting' + write_file(ready_path) + wait_file(sync_path, timeout=timeout) + + def _timeout_factor(): """return the current modification to timeout""" default = int(environ.get('HGTEST_TIMEOUT_DEFAULT', 360))
--- a/rust/hg-core/src/utils.rs Tue Feb 28 00:04:32 2023 +0100 +++ b/rust/hg-core/src/utils.rs Tue Feb 28 00:01:41 2023 +0100 @@ -15,6 +15,7 @@ use std::fmt; use std::{io::Write, ops::Deref}; +pub mod debug; pub mod files; pub mod hg_path; pub mod path_auditor;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/utils/debug.rs Tue Feb 28 00:01:41 2023 +0100 @@ -0,0 +1,81 @@ +//! Utils for debugging hg-core + +use crate::config::Config; + +/// Write the file path given by the config option `devel.<config_option>` with +/// the suffix `.waiting`, then wait for the file path given by the +/// config option `devel.<config_option>` to appear on disk +/// up to `devel.<config_option>-timeout` seconds. +/// Note that the timeout may be higher because we scale it if global +/// `run-tests` timeouts are raised to prevent flakiness on slower hardware. +/// +/// Useful for testing race conditions. +pub fn debug_wait_for_file( + config: &Config, + config_option: &str, +) -> Result<(), String> { + let path_opt = format!("sync.{config_option}"); + let file_path = match config.get_str(b"devel", path_opt.as_bytes()).ok() { + Some(Some(file_path)) => file_path, + _ => return Ok(()), + }; + + // TODO make it so `configitems` is shared between Rust and Python so that + // defaults work out of the box, etc. + let default_timeout = 2; + let timeout_opt = format!("sync.{config_option}-timeout"); + let timeout_seconds = + match config.get_u32(b"devel", timeout_opt.as_bytes()) { + Ok(Some(timeout)) => timeout, + Err(e) => { + log::debug!("{e}"); + default_timeout + } + _ => default_timeout, + }; + let timeout_seconds = timeout_seconds as u64; + + log::debug!( + "Config option `{config_option}` found, \ + waiting for file `{file_path}` to be created" + ); + std::fs::File::create(format!("{file_path}.waiting")).ok(); + // If the test timeout have been extended, scale the timer relative + // to the normal timing. + let global_default_timeout: u64 = std::env::var("HGTEST_TIMEOUT_DEFAULT") + .map(|t| t.parse()) + .unwrap_or(Ok(0)) + .unwrap(); + let global_timeout_override: u64 = std::env::var("HGTEST_TIMEOUT") + .map(|t| t.parse()) + .unwrap_or(Ok(0)) + .unwrap(); + let timeout_seconds = if global_default_timeout < global_timeout_override { + timeout_seconds * global_timeout_override / global_default_timeout + } else { + timeout_seconds + }; + let timeout = std::time::Duration::from_secs(timeout_seconds); + + let start = std::time::Instant::now(); + let path = std::path::Path::new(file_path); + let mut found = false; + while start.elapsed() < timeout { + if path.exists() { + log::debug!("File `{file_path}` was created"); + found = true; + break; + } else { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + } + if !found { + let msg = format!( + "File `{file_path}` set by `{config_option}` was not found \ + within the allocated {timeout_seconds} seconds timeout" + ); + Err(msg) + } else { + Ok(()) + } +}