test_binary_features/
group.rs

1use crate::indicators::{BinaryCrateName, GroupEnd, SpawningMode};
2use crate::output::{DynErr, DynErrResult, OptOutput, ProcessOutput};
3use crate::task;
4use core::borrow::Borrow;
5use core::ops::{Deref, DerefMut};
6use core::time::Duration;
7use phantom_newtype::Id;
8use std::collections::HashMap;
9use std::error::Error as StdError;
10use std::io::{self, Result as IoResult, Write};
11use std::process::Child;
12use std::thread;
13
14/// How long to sleep before checking again whether any child process(es) finished.
15const SLEEP_BETWEEN_CHECKING_CHILDREN: Duration = Duration::from_millis(10);
16
17/// For disambiguation.
18pub type ChildProcess = Child;
19
20/// Result of [Child]'s `id()` method, wrapped.
21//#[repr(transparent)]
22//pub sttrans ChildProcessId(pub u32);
23pub type ChildProcessId = Id<ChildProcess, u32>;
24
25pub type ChildInfo = String;
26
27/// Having a struct with (one) named field (for example, `its`), instead of a (one field) tuple
28/// struct, could make some code "nicer".
29/// ```rust
30/// #[repr(transparent)]
31/// pub struct ChildInfoMeta<M> {
32///     /* Not called `it`, so as not to confuse it with iterators.*/
33///     pub its: (M, /*...*/),
34/// }
35/// ```
36/// However, that would be useful mostly for either
37/// - accessing the (only) field, but that we can also dereference with asterisk (through [Deref]
38///   and [DerefMut]). Or
39/// - accessing the anonymous/positional field(s) of the underlying tuple. But for that we have our
40///   named accessor methods, so we don't need to access it through/by specifying the wrapped tuple
41///   itself. So we don't need any of that much.
42///
43/// Instead, the consumers benefit more from easily destructuring our wrappers struct into its
44/// (only) positional/anonymous field, especially so they destructure the underlying tuple into its
45/// fields (and give them names as local variables).
46///
47/// Why anonymous tuples (with nameless fields)? Brevity of positional constructor. And pattern matching.
48pub struct ChildInfoMeta<M>(ChildProcess, ChildInfo, M);
49impl<M> ChildInfoMeta<M> {
50    /// Useful if we don't want to publish the wrapped field.
51    /*pub fn new(process: ChildProcess, info: ChildInfo, meta: M) -> Self {
52        Self((process, info, meta))
53    }*/
54
55    pub fn child(&self) -> &ChildProcess {
56        &self.0
57    }
58    pub fn info(&self) -> &ChildInfo {
59        &self.1
60    }
61    /// Only for non-[Copy], or if [Copy] but deemed large.
62    ///
63    /// If [Copy] and primitive, then this function doesn't return a reference, but the field value.
64    pub fn meta(&self) -> &M {
65        &self.2
66    }
67
68    // For [Copy] but deemed large.
69    //
70    // pub fn meta_copy(&self) -> M
71    //
72    // Suggest not to have `meta_ref(&self) ->&M for a reference to (deemed large) M. Why? Because
73    // sharing & accessing by references is preferred to copying the data (wherever possible). So we
74    // make this preferred method name a short one.
75
76    pub fn meta_mut(&mut self) -> &mut M {
77        &mut self.2
78    }
79}
80
81/// Only for [Copy], and only if NOT primitive.
82///
83/// Rename to `meta_copy()` for non-primitive types, so that its cost would be evident everywhere
84/// it's used.
85impl<M> ChildInfoMeta<M>
86where
87    M: Copy,
88{
89    pub fn meta_copy(&self) -> M {
90        self.2
91    }
92}
93
94/// Implementing [Deref] and [DerefMut] doesn't improve ergonomics for accessing the tuple's field
95/// by numeric index. For that we have to put the dereferencing asterisk and the struct instance
96/// into parenthesis, e.g.
97///
98/// let wrap: ChildInfoMetaWrap = ...;
99/// meta = (*wrap).2;
100///
101/// So we may just as well use
102///
103/// let wrap: ChildInfoMetaWrap = ...;
104/// meta = wrap.0.2;
105///
106/// - a little easier to type and read. However, we really want to use our named accessor methods.
107///
108/// But dereferencing with asterisk is ergonomic when we need to cast the struct back to the tuple
109/// type.
110
111/// Group of active (running) Child processes.
112///
113/// NOT [std::collections::HashSet], because that makes referencing the items less efficient.
114///
115/// Keys are results of [ChildProcess]'s `id()` method.
116pub type GroupOfChildren<M> = HashMap<ChildProcessId, ChildInfoMeta<M>>;
117
118pub type Features<'a, S> //where S: ?Sized,
119    = Vec<&'a S /* feature */>;
120
121pub type ParallelTasks<'a, S, M> = Vec<(
122    &'a S, /* subdir */
123    &'a BinaryCrateName<'a, S>,
124    Features<'a, S>,
125    ChildInfo,
126    M,
127)>;
128
129pub(crate) type GroupExecution<M> = (GroupOfChildren<M>, SpawningMode);
130pub(crate) type GroupOfChildrenAndOptOutput<M> = (GroupOfChildren<M>, OptOutput<M>);
131pub(crate) type GroupExecutionAndStartErrors<M> = (GroupExecution<M>, Vec<DynErr>);
132
133/// Start a group of parallel child process(es) - tasks, all under the same `parent_dir`.
134///
135/// This does NOT have a [crate::indicators::SpawningMode] parameter - we behave as if under
136/// [crate::indicators::SpawningMode::ProcessAll].
137///
138/// This does NOT check for exit status/stderr of any spawn child processes. It only checks if the
139/// actual spawning itself (system call) was successful. If all spawn successfully, then the
140/// [crate::indicators::SpawningMode] of the result tuple is [SpawningMode::ProcessAll]. Otherwise
141/// the [crate::indicators::SpawningMode] part of the result tuple is either
142/// [crate::indicators::SpawningMode::FinishActive] or [crate::indicators::SpawningMode::StopAll],
143/// depending on the given `until` ([GroupEnd]).
144pub fn start_parallel_tasks<'a, S, M>(
145    tasks: ParallelTasks<'a, S, M>,
146    parent_dir: &'a S,
147    until: &'a GroupEnd,
148) -> GroupExecutionAndStartErrors<M>
149//@TODO change the output type to be only a Vec<DynErr>.
150where
151    S: Borrow<str> + 'a + ?Sized,
152    &'a S: Borrow<str>,
153{
154    let mut children = GroupOfChildren::new();
155    let mut spawning_mode = SpawningMode::default();
156    let mut errors = Vec::with_capacity(0);
157
158    for (sub_dir, binary_crate, features, child_info, meta) in tasks {
159        let child_or_err = task::spawn(parent_dir, sub_dir, binary_crate, &features);
160
161        match child_or_err {
162            Ok(child) => {
163                children.insert(child.id().into(), ChildInfoMeta(child, child_info, meta));
164            }
165            Err(err) => {
166                spawning_mode = until.mode_after_error_in_same_group();
167                errors.push(err);
168            }
169        };
170    }
171    ((children, spawning_mode), errors)
172}
173
174/// Iterate over the given children max. once. Take the first finished child (if any), and return
175/// its process ID and exit status.
176///
177/// The [ChildId] is child process ID of the finished process.
178///
179/// Beware: [Ok] of [Some] actually CAN contain [ExitStatus] _NOT_ being OK!
180pub(crate) fn try_finished_child<M>(
181    children: &mut GroupOfChildren<M>,
182) -> DynErrResult<Option<ChildProcessId>> {
183    for (child_id, ChildInfoMeta(child, _, _)) in children.iter_mut() {
184        let opt_status_or_err = child.try_wait();
185
186        match opt_status_or_err {
187            Ok(Some(_exit_status)) => {
188                return Ok(Some(*child_id));
189            }
190            Ok(None) => {}
191            Err(err) => return Err(Box::new(err)),
192        }
193    }
194    Ok(None)
195}
196
197pub(crate) fn print_output(output: &ProcessOutput) -> IoResult<()> {
198    // If we have both non-empty stdout and stderr, print stdout first, and stderr second. That way
199    // the developer is more likely to notice (and there is less vertical distance to scroll up).
200    {
201        let mut stdout = io::stdout().lock();
202        // @TODO print process "name"
203        write!(stdout, "Exit status: {}", output.status)?;
204        stdout.write_all(&output.stdout)?;
205    }
206    {
207        let mut stderr = io::stderr().lock();
208        stderr.write_all(&output.stderr)?;
209        if !output.stderr.is_empty() {
210            stderr.flush()?;
211        }
212    }
213    Ok(())
214}
215
216/// Return [Some] if any child has finished; return [None] when all children have finished. This
217/// does NOT modify [SpawningMode] part of the result [GroupExecutionAndOptOutput].
218#[must_use]
219pub fn collect_finished_child<M>(
220    mut children: GroupOfChildren<M>,
221) -> Option<GroupOfChildrenAndOptOutput<M>> {
222    let finished_result = try_finished_child(&mut children);
223    match finished_result {
224        Ok(Some(child_id)) => {
225            let ChildInfoMeta(child, child_info, meta) = children.remove(&child_id).unwrap();
226            let (child_output, err) = match child.wait_with_output() {
227                Ok(child_output) => (Some(child_output), None),
228                Err(err) => (
229                    None,
230                    Some({
231                        let err: Box<dyn StdError> = Box::new(err);
232                        err
233                    }),
234                ),
235            };
236            Some((
237                children,
238                Some((Some((child_output, child_info, meta)), err)),
239            ))
240        }
241        Ok(None) => {
242            if children.is_empty() {
243                None
244            } else {
245                Some((children, None))
246            }
247        }
248        Err(err) => Some((children, Some((None, Some(err))))),
249    }
250}
251
252#[must_use]
253pub fn life_cycle_step<M>(
254    (mut _children, mut _spawning_mode): GroupExecution<M>,
255    _until: &GroupEnd,
256) -> DynErrResult<()> {
257    /*match collect_finished_child(children) {
258
259    }*/
260    thread::sleep(SLEEP_BETWEEN_CHECKING_CHILDREN);
261    // @TODO kill
262    panic!()
263}
264
265#[must_use]
266pub fn life_cycle_loop<M>(
267    (mut _children, mut _spawning_mode): GroupExecution<M>,
268    _until: &GroupEnd,
269) -> DynErrResult<()> {
270    loop {
271        panic!();
272    }
273}