summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2024-01-10 17:31:14 +0200
committerLars Wirzenius <liw@liw.fi>2024-01-10 17:38:05 +0200
commit87f91e2474a36910228430bd4d5ce8700d86eab4 (patch)
tree1f14a02642a4d35661e621e25ea2be7a336cd538
parentddfedf5985b4da1fa37178ad2162861c4fa711c3 (diff)
downloadradicle-native-ci-87f91e2474a36910228430bd4d5ce8700d86eab4.tar.gz
feat: allow setting max duration of a CI run
Signed-off-by: Lars Wirzenius <liw@liw.fi>
-rw-r--r--src/bin/radicle-native-ci.rs20
-rw-r--r--src/config.rs10
-rwxr-xr-xtest-suite15
3 files changed, 43 insertions, 2 deletions
diff --git a/src/bin/radicle-native-ci.rs b/src/bin/radicle-native-ci.rs
index 0c3910c..016a4fb 100644
--- a/src/bin/radicle-native-ci.rs
+++ b/src/bin/radicle-native-ci.rs
@@ -111,6 +111,7 @@ fn fallible_main_inner(
.src(&src)
.log(logfile)
.run_log(&run_log)
+ .timeout(config.timeout)
.builder(builder)
.build()?;
let result = runner.run();
@@ -155,6 +156,7 @@ struct Runner<'a> {
src: PathBuf,
log: &'a mut LogFile,
run_log: LogFile,
+ timeout: Option<usize>,
builder: &'a mut RunInfoBuilder,
}
@@ -211,7 +213,16 @@ impl<'a> Runner<'a> {
debug!("running CI in cloned repository");
self.log.writeln("run shell snippet in repository")?;
let snippet = format!("set -xeuo pipefail\n{}", &runspec.shell);
- runcmd(&mut self.run_log, &["bash", "-c", &snippet], &self.src)?;
+ if let Some(timeout) = self.timeout {
+ let timeout = format!("{}", timeout);
+ runcmd(
+ &mut self.run_log,
+ &["timeout", &timeout, "bash", "-c", &snippet],
+ &self.src,
+ )?;
+ } else {
+ runcmd(&mut self.run_log, &["bash", "-c", &snippet], &self.src)?;
+ }
let result = RunResult::Success;
@@ -237,6 +248,7 @@ struct RunnerBuilder<'a> {
src: Option<PathBuf>,
log: Option<&'a mut LogFile>,
run_log: Option<PathBuf>,
+ timeout: Option<usize>,
builder: Option<&'a mut RunInfoBuilder>,
}
@@ -276,6 +288,11 @@ impl<'a> RunnerBuilder<'a> {
self
}
+ fn timeout(mut self, timeout: Option<usize>) -> Self {
+ self.timeout = timeout;
+ self
+ }
+
fn builder(mut self, builder: &'a mut RunInfoBuilder) -> Self {
self.builder = Some(builder);
self
@@ -292,6 +309,7 @@ impl<'a> RunnerBuilder<'a> {
src: self.src.ok_or(NativeError::Unset("src"))?,
log: self.log.ok_or(NativeError::Unset("log"))?,
run_log,
+ timeout: self.timeout,
builder: self.builder.ok_or(NativeError::Unset("builder"))?,
})
}
diff --git a/src/config.rs b/src/config.rs
index e9642ab..68401c7 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,6 +5,8 @@ use serde::Deserialize;
use crate::logfile::{LogError, LogFile};
+const DEFAULT_TIMEOUT: usize = 3600;
+
/// Configuration file for `radicle-native-ci`.
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
@@ -15,6 +17,9 @@ pub struct Config {
/// File where native CI should write a log.
pub log: PathBuf,
+
+ /// Optional maximum duration of a CI run.
+ pub timeout: Option<usize>,
}
impl Config {
@@ -35,8 +40,11 @@ impl Config {
pub fn read(filename: &Path) -> Result<Self, ConfigError> {
let file = std::fs::File::open(filename)
.map_err(|e| ConfigError::ReadConfig(filename.into(), e))?;
- let config = serde_yaml::from_reader(&file)
+ let mut config: Self = serde_yaml::from_reader(&file)
.map_err(|e| ConfigError::ParseConfig(filename.into(), e))?;
+ if config.timeout.is_none() {
+ config.timeout = Some(DEFAULT_TIMEOUT);
+ }
Ok(config)
}
}
diff --git a/test-suite b/test-suite
index 8aee7e5..b9f34a9 100755
--- a/test-suite
+++ b/test-suite
@@ -207,6 +207,19 @@ class Suite:
assert "shell" in stderr
assert "string" in stderr
+ def test_command_takes_too_long(self):
+ git = self._create_git_repo("command-takes-too-long")
+ self._create_valid_native_yaml(git, "sleep 5")
+ rid, commit = self._get_repo_info(git)
+ trigger = Trigger(rid, commit)
+ ci = self._create_ci()
+ exit, resps, stderr = ci.run(trigger)
+ assert exit != 0
+ assert len(resps) == 2
+ self.assert_triggered(resps[0])
+ self.assert_error(resps[1])
+ assert "124" in stderr
+
class Git:
def __init__(self, path):
@@ -281,6 +294,7 @@ class Config:
self.dict = {
"state": os.path.join(tmp, "state"),
"log": os.path.join(tmp, "node-log.txt"),
+ "timeout": 2,
}
def write(self):
@@ -325,6 +339,7 @@ class NativeCI:
"RADICLE_NATIVE_CI": self.config,
"RADICLE_NATIVE_CI_LOG": "debug",
}
+ self.timeout = 1
def without_config(self):
del self.env["RADICLE_NATIVE_CI"]