Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
radicle-ci-broker src bin cibtoolcmd timeout.rs
use std::{
    process::Command,
    thread::sleep,
    time::{Duration, Instant},
};

use radicle_ci_broker::timeoutcmd::TimeoutCommand;

use super::*;

/// Trigger a CI run.
///
/// This is meant for developer experimentation.
#[derive(Parser)]
pub struct TimeoutCmd {
    /// A Bash script to run. Should start with "exec", or time out won't work.
    #[clap(long)]
    script: String,

    /// Text to be fed to script via stdin.
    #[clap(long, default_value = "")]
    stdin: String,

    /// Generate at least this much data to feed to script via stdin.
    #[clap(long)]
    generate: Option<usize>,

    /// Terminate script after this many seconds.
    #[clap(long)]
    timeout: u64,

    /// Verbose output: show stdout and stderr output lines.
    #[clap(short, long)]
    verbose: bool,

    /// Don't empty stdout and stderr buffers, let them fill up.
    #[clap(long)]
    fill_buffers: bool,

    /// Kill script after this many seconds, unconditionally.
    #[clap(long)]
    kill_after: Option<u64>,
}

impl Leaf for TimeoutCmd {
    fn run(&self, _args: &Args) -> Result<(), CibToolError> {
        let mut cmd = Command::new("bash");
        cmd.arg("-c").arg(&self.script);

        let mut to = TimeoutCommand::new(Duration::from_secs(self.timeout));
        let mut stdout = to.stdout();

        if let Some(bytes) = self.generate {
            let mut stdin: Vec<u8> = vec![];
            while stdin.len() < bytes {
                for byte in b"hello, world\n" {
                    stdin.push(*byte);
                }
            }
            to.feed_stdin(stdin.as_slice());
            println!("generated stdin has {} bytes", stdin.len());
        } else {
            to.feed_stdin(self.stdin.as_bytes());
        }

        let started = Instant::now();
        println!("spawn child");
        let running = to.spawn(cmd)?;

        let mut stdout_bytes = 0;
        let tor = if let Some(secs) = self.kill_after {
            sleep(Duration::from_secs(secs));
            running.kill()?
        } else {
            if !self.fill_buffers {
                while let Some(line) = stdout.line() {
                    stdout_bytes += line.len();
                    if self.verbose {
                        println!("stdout: {line:?}");
                    }
                }
                println!("finished reading stdout");
            }

            running.wait()?
        };

        let stderr = tor.stderr();
        for line in stderr {
            if self.verbose {
                println!("stderr: {line:?}");
            }
        }
        println!("finished reading stderr");

        let elapsed = started.elapsed();
        let speed = (stdout_bytes as f64) / elapsed.as_secs_f64();

        println!("stdout bytes: {stdout_bytes}");
        println!("duration: {} ms", elapsed.as_millis());
        println!("speed: {speed:.0} B/s");
        println!("exit: {}", tor.exit_code());

        Ok(())
    }
}