Move Lua VM ownership from Pipeline to Runtime for execution
Runtime now owns the Fennel/Lua VM, constructed by consuming the
Pipeline. Source outputs (quire/push) are built as native Lua tables
instead of going through serde — removes the InputOutputs/PushOutputs
types entirely. Pipeline is now purely structural after parsing;
execution transfers its VM to the runtime.
Assisted-by: GLM-5.1 via pi
diff --git a/src/bin/quire/commands/ci.rs b/src/bin/quire/commands/ci.rs
index 1818805..9c32c77 100644
--- a/src/bin/quire/commands/ci.rs
+++ b/src/bin/quire/commands/ci.rs
@@ -73,17 +73,20 @@ pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
pushed_at: jiff::Timestamp::now(),
};
+ let job_ids: Vec<String> = pipeline.jobs().iter().map(|j| j.id.clone()).collect();
+
let mut run = runs.create(&meta)?;
println!("Run {}: executing at {}", run.id(), commit.display);
- let exec_result = run.execute(&pipeline, secrets);
+ let exec_result = run.execute(pipeline, secrets);
- for job in pipeline.jobs() {
- let outputs = run.outputs(&job.id);
+ // Pipeline was consumed by execute. Output display uses run.outputs()
+ for job_id in &job_ids {
+ let outputs = run.outputs(job_id);
if outputs.is_empty() {
continue;
}
- println!("\n==> {}", job.id);
+ println!("\n==> {}", job_id);
for o in &outputs {
if !o.stdout.is_empty() {
print!("{}", o.stdout);
diff --git a/src/ci/lua.rs b/src/ci/lua.rs
index c993570..fccd1cc 100644
--- a/src/ci/lua.rs
+++ b/src/ci/lua.rs
@@ -8,7 +8,7 @@
//! each `run-fn` at execute time.
use std::cell::RefCell;
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use mlua::{IntoLua, Lua, LuaSerdeExt};
@@ -93,29 +93,9 @@ fn register_job(
Ok(())
}
-/// Outputs of the `:quire/push` source, exposed to `(jobs :quire/push)`.
-/// For v1: `:sha`, `:ref`, `:pushed-at`. Branch/tag/etc are deferred.
-#[derive(Clone, Debug, serde::Serialize)]
-pub(super) struct PushOutputs {
- pub sha: String,
- #[serde(rename = "ref")]
- pub r#ref: String,
- #[serde(rename = "pushed-at")]
- pub pushed_at: jiff::Timestamp,
-}
-
-/// What `(jobs name)` resolves to. One variant per kind of input;
-/// `untagged` so the on-the-Lua-side shape is the inner struct
-/// directly, not a wrapper table with a tag field.
-#[derive(Clone, Debug, serde::Serialize)]
-#[serde(untagged)]
-pub(super) enum InputOutputs {
- Push(PushOutputs),
-}
-
-/// Per-execution runtime: holds the secrets exposed to the job, the
-/// per-job `(jobs name)` views, the current-job cursor, and the
-/// per-job captured `sh` outputs.
+/// Per-execution runtime: owns the Lua VM, holds the secrets exposed
+/// to the job, the per-job `(jobs name)` views, the current-job
+/// cursor, and the per-job captured `sh` outputs.
///
/// `inputs` is keyed by the calling job; each inner map covers
/// exactly the names that job may read. Reachability is implicit in
@@ -135,23 +115,53 @@ pub(super) enum InputOutputs {
/// runs the command but doesn't record (the cursor lookup misses).
/// `(secret …)` and `(jobs …)` require a runtime — without one, calls
/// error.
-#[derive(Debug, Default)]
pub(super) struct Runtime {
+ fennel: Fennel,
secrets: HashMap<String, SecretString>,
- inputs: HashMap<String, HashMap<String, Option<InputOutputs>>>,
+ inputs: HashMap<String, HashMap<String, Option<mlua::Value>>>,
current_job: RefCell<Option<String>>,
outputs: RefCell<HashMap<String, Vec<ShOutput>>>,
}
impl Runtime {
- /// Build a fresh runtime. `inputs` is the precomputed per-job
- /// `(jobs name)` view from [`Pipeline::transitive_inputs`] and
- /// the source outputs.
+ /// Build a fresh runtime owning `fennel` (the Lua VM).
+ ///
+ /// `meta` provides the push data for `:quire/push` source outputs.
+ /// `transitive` maps each job to its set of reachable input names;
+ /// the runtime builds per-job views from this and the source values.
pub(super) fn new(
+ fennel: Fennel,
secrets: HashMap<String, SecretString>,
- inputs: HashMap<String, HashMap<String, Option<InputOutputs>>>,
+ meta: &super::run::RunMeta,
+ transitive: &HashMap<String, HashSet<String>>,
) -> Self {
+ let lua = fennel.lua();
+
+ // Build the push outputs as a Lua table.
+ let push = lua.create_table().expect("create push table");
+ push.set("sha", meta.sha.as_str()).expect("set sha");
+ push.set("ref", meta.r#ref.as_str()).expect("set ref");
+ push.set("pushed-at", meta.pushed_at.to_string().as_str())
+ .expect("set pushed-at");
+ let push_value = push.into_lua(lua).expect("push table to value");
+
+ // Build per-job input views from transitive reachability.
+ let mut inputs = HashMap::new();
+ for (job_id, reachable) in transitive {
+ let mut view = HashMap::new();
+ for name in reachable {
+ let value = if name == "quire/push" {
+ Some(push_value.clone())
+ } else {
+ None
+ };
+ view.insert(name.clone(), value);
+ }
+ inputs.insert(job_id.clone(), view);
+ }
+
Self {
+ fennel,
secrets,
inputs,
current_job: RefCell::new(None),
@@ -159,6 +169,11 @@ impl Runtime {
}
}
+ /// Borrow the underlying Lua VM.
+ pub(super) fn lua(&self) -> &Lua {
+ self.fennel.lua()
+ }
+
/// Mark `id` as the currently executing job. `(sh …)` invocations
/// from this job's `run_fn` will record output under `id`, and
/// `(jobs …)` lookups will resolve against `id`'s view.
@@ -179,6 +194,20 @@ impl Runtime {
}
}
+#[cfg(test)]
+impl Runtime {
+ /// Minimal constructor for tests — no inputs, no source outputs.
+ fn for_test(fennel: Fennel, secrets: HashMap<String, SecretString>) -> Self {
+ Self {
+ fennel,
+ secrets,
+ inputs: HashMap::new(),
+ current_job: RefCell::new(None),
+ outputs: RefCell::new(HashMap::new()),
+ }
+ }
+}
+
/// `IntoLua` carrier for an `Rc<Runtime>`. Stows the Rc on the VM as
/// app data and returns the handle table — `{sh, secret, jobs}`.
pub(super) struct RuntimeHandle(pub Rc<Runtime>);
@@ -194,11 +223,11 @@ impl IntoLua for RuntimeHandle {
}
}
-/// Body of `(jobs name)`. Returns the typed outputs the calling job's
-/// view has for `name`, serialized to a Lua value via serde. Reachable
-/// names without recorded outputs come back as `Nil`. Errors if `name`
-/// is outside the calling job's view, if the calling job tries to read
-/// its own outputs, or if the runtime isn't installed.
+/// Body of `(jobs name)`. Returns the outputs the calling job's
+/// view has for `name` as a Lua value. Reachable names without
+/// recorded outputs come back as `nil`. Errors if `name` is outside
+/// the calling job's view, if the calling job tries to read its own
+/// outputs, or if the runtime isn't installed.
fn lookup_input(lua: &Lua, name: String) -> mlua::Result<mlua::Value> {
let rt = lua
.app_data_ref::<Rc<Runtime>>()
@@ -211,7 +240,7 @@ fn lookup_input(lua: &Lua, name: String) -> mlua::Result<mlua::Value> {
mlua::Error::external(format!("no inputs view for calling job '{calling}'"))
})?;
match view.get(&name) {
- Some(Some(value)) => lua.to_value(value),
+ Some(Some(value)) => Ok(value.clone()),
Some(None) => Ok(mlua::Value::Nil),
None if name == *calling => Err(mlua::Error::external(format!(
"Job '{calling}' cannot read its own outputs"
@@ -377,13 +406,18 @@ mod tests {
use super::super::pipeline::Pipeline;
use super::*;
- /// Install a runtime with the given secrets on `pipeline`'s VM and
- /// return the runtime handle. Mirrors what `Run::execute` does so
- /// tests can drive a `run_fn` directly.
- fn rt(pipeline: &Pipeline, secrets: HashMap<String, SecretString>) -> mlua::Value {
- RuntimeHandle(Rc::new(Runtime::new(secrets, HashMap::new())))
- .into_lua(pipeline.fennel().lua())
- .expect("install runtime")
+ /// Extract the first job's `run_fn` from the pipeline, consume the
+ /// pipeline for its VM, build a minimal runtime, and return the
+ /// runtime handle plus the run_fn.
+ fn rt(source: &str, secrets: HashMap<String, SecretString>) -> (Rc<Runtime>, mlua::Function) {
+ let pipeline = Pipeline::load(source, "ci.fnl").expect("load should succeed");
+ let run_fn = pipeline.jobs()[0].run_fn.clone();
+ let fennel = pipeline.into_fennel();
+ let runtime = Rc::new(Runtime::for_test(fennel, secrets));
+ let _ = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ (runtime, run_fn)
}
#[test]
@@ -395,10 +429,12 @@ mod tests {
);
let source = r#"(local ci (require :quire.ci))
(ci.job :grab [:quire/push] (fn [{: secret}] (secret :github_token)))"#;
- let pipeline = Pipeline::load(source, "ci.fnl").expect("load should succeed");
- let token: String = pipeline.jobs()[0]
- .run_fn
- .call(rt(&pipeline, secrets))
+ let (runtime, run_fn) = rt(source, secrets);
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let token: String = run_fn
+ .call(handle)
.expect("run_fn should return the secret value");
assert_eq!(token, "ghp_test_value");
}
@@ -407,11 +443,11 @@ mod tests {
fn secret_errors_for_unknown_name() {
let source = r#"(local ci (require :quire.ci))
(ci.job :grab [:quire/push] (fn [{: secret}] (secret :missing)))"#;
- let pipeline = Pipeline::load(source, "ci.fnl").expect("load should succeed");
- let err = pipeline.jobs()[0]
- .run_fn
- .call::<mlua::Value>(rt(&pipeline, HashMap::new()))
- .unwrap_err();
+ let (runtime, run_fn) = rt(source, HashMap::new());
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let err = run_fn.call::<mlua::Value>(handle).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("unknown secret") && msg.contains("missing"),
@@ -421,18 +457,14 @@ mod tests {
/// Build a pipeline whose single job's run-fn invokes `(sh …)`,
/// invoke it with the runtime handle, and decode the resulting Lua
- /// table as ShOutput through the pipeline's VM via `lua.from_value`.
+ /// table as ShOutput.
fn run_sh_via_job(source: &str) -> ShOutput {
- let pipeline = Pipeline::load(source, "ci.fnl").expect("load should succeed");
- let value: mlua::Value = pipeline.jobs()[0]
- .run_fn
- .call(rt(&pipeline, HashMap::new()))
- .expect("sh call should return a value");
- pipeline
- .fennel()
- .lua()
- .from_value(value)
- .expect("decode ShOutput")
+ let (runtime, run_fn) = rt(source, HashMap::new());
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let value: mlua::Value = run_fn.call(handle).expect("sh call should return a value");
+ runtime.lua().from_value(value).expect("decode ShOutput")
}
#[test]
@@ -499,16 +531,15 @@ mod tests {
#[test]
fn sh_rejects_unknown_opt_key() {
- let pipeline = Pipeline::load(
+ let (runtime, run_fn) = rt(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [{: sh}] (sh "echo hi" {:cwdir "/tmp"})))"#,
- "ci.fnl",
- )
- .expect("load should succeed");
- let err = pipeline.jobs()[0]
- .run_fn
- .call::<mlua::Value>(rt(&pipeline, HashMap::new()))
- .unwrap_err();
+ HashMap::new(),
+ );
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let err = run_fn.call::<mlua::Value>(handle).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("unknown field") && msg.contains("cwdir"),
@@ -518,16 +549,15 @@ mod tests {
#[test]
fn sh_rejects_non_sequence_table_as_cmd() {
- let pipeline = Pipeline::load(
+ let (runtime, run_fn) = rt(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [{: sh}] (sh {:env {:FOO "bar"}})))"#,
- "ci.fnl",
- )
- .expect("load should succeed");
- let err = pipeline.jobs()[0]
- .run_fn
- .call::<mlua::Value>(rt(&pipeline, HashMap::new()))
- .unwrap_err();
+ HashMap::new(),
+ );
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let err = run_fn.call::<mlua::Value>(handle).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("sequence"),
@@ -537,16 +567,15 @@ mod tests {
#[test]
fn sh_rejects_empty_argv() {
- let pipeline = Pipeline::load(
+ let (runtime, run_fn) = rt(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [{: sh}] (sh [])))"#,
- "ci.fnl",
- )
- .expect("load should succeed");
- let err = pipeline.jobs()[0]
- .run_fn
- .call::<mlua::Value>(rt(&pipeline, HashMap::new()))
- .unwrap_err();
+ HashMap::new(),
+ );
+ let handle = RuntimeHandle(runtime.clone())
+ .into_lua(runtime.lua())
+ .expect("install runtime");
+ let err = run_fn.call::<mlua::Value>(handle).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("empty"),
diff --git a/src/ci/pipeline.rs b/src/ci/pipeline.rs
index e8b77d0..fb1bdac 100644
--- a/src/ci/pipeline.rs
+++ b/src/ci/pipeline.rs
@@ -101,10 +101,13 @@ impl Pipeline {
.map(|&idx| &self.jobs[self.graph[idx]])
}
- /// Borrow the underlying Fennel/Lua VM. Used by the executor to
- /// install runtime state on the VM before invoking job `run_fn`s.
- pub(crate) fn fennel(&self) -> &Fennel {
- &self.fennel
+ /// Consume the pipeline and return its Fennel/Lua VM.
+ ///
+ /// The job functions (`run_fn`) are `'static` handles into this VM,
+ /// so they remain callable after extraction as long as the VM stays
+ /// alive.
+ pub(crate) fn into_fennel(self) -> Fennel {
+ self.fennel
}
/// Return job IDs in topological order — dependencies before
diff --git a/src/ci/run.rs b/src/ci/run.rs
index fa55ae9..ec5f75c 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -12,7 +12,7 @@ use std::rc::Rc;
use jiff::Timestamp;
use mlua::IntoLua;
-use super::lua::{InputOutputs, PushOutputs, Runtime, RuntimeHandle, ShOutput};
+use super::lua::{Runtime, RuntimeHandle, ShOutput};
use super::pipeline::Pipeline;
use crate::secret::SecretString;
use crate::{Error, Result};
@@ -221,7 +221,8 @@ pub struct Run {
/// secrets exposed to the script, tracks the currently-running
/// job, and accumulates per-job captured `sh` output. Replaced
/// fresh each `execute` call so secrets are scoped to that call.
- runtime: Rc<Runtime>,
+ /// `None` before `execute` is called.
+ runtime: Option<Rc<Runtime>>,
}
impl Run {
@@ -250,7 +251,7 @@ impl Run {
base,
state,
id,
- runtime: Rc::new(Runtime::default()),
+ runtime: None,
};
run.read_meta()?;
run.read_times()?;
@@ -259,59 +260,76 @@ impl Run {
/// Drive `pipeline` to completion through this run.
///
- /// Constructs a fresh [`Runtime`] with `secrets`, the source
- /// outputs (`:quire/push` from `meta.yml`), and the per-job
- /// transitive-input sets; installs it on the pipeline's Lua VM,
- /// topo-sorts the jobs, transitions Pending → Active, then
- /// invokes each `run_fn` in dependency order with the runtime
- /// handle as its sole argument. `(sh …)` calls record their
- /// captured output under the current job — readable via
- /// [`Run::outputs`] after `execute` returns. The run finishes
- /// in `Complete` if every job's `run_fn` returned without error,
+ /// Consumes the pipeline, taking ownership of its Lua VM. Constructs
+ /// a fresh [`Runtime`] with `secrets`, the source outputs
+ /// (`:quire/push` from `meta.yml`), and the per-job transitive-input
+ /// sets; installs it on the VM, topo-sorts the jobs, transitions
+ /// Pending → Active, then invokes each `run_fn` in dependency order
+ /// with the runtime handle as its sole argument. `(sh …)` calls
+ /// record their captured output under the current job — readable via
+ /// [`Run::outputs`] after `execute` returns. The run finishes in
+ /// `Complete` if every job's `run_fn` returned without error,
/// otherwise `Failed`.
///
/// Source-ref filtering (e.g. running only `quire/push`-reachable
/// jobs) is not yet implemented; for now every validated job runs.
pub fn execute(
&mut self,
- pipeline: &Pipeline,
+ pipeline: Pipeline,
secrets: HashMap<String, SecretString>,
) -> Result<()> {
- let lua = pipeline.fennel().lua();
let meta = self.read_meta()?;
- let sources = source_outputs(&meta);
- let inputs = build_inputs_views(&pipeline.transitive_inputs(), &sources);
- self.runtime = Rc::new(Runtime::new(secrets, inputs));
- let rt_value = RuntimeHandle(self.runtime.clone())
- .into_lua(lua)
- .expect("install runtime on Lua VM");
- let order: Vec<String> = pipeline
+ // Extract execution plan before consuming the pipeline for its VM.
+ let topo: Vec<String> = pipeline
.topo_order()
.into_iter()
.map(String::from)
.collect();
+ let transitive = pipeline.transitive_inputs();
+ let run_fns: Vec<(String, mlua::Function)> = topo
+ .iter()
+ .map(|id| {
+ let job = pipeline
+ .job(id)
+ .expect("topo_order returned a job id not in pipeline");
+ (job.id.clone(), job.run_fn.clone())
+ })
+ .collect();
- self.transition(RunState::Active)?;
+ let fennel = pipeline.into_fennel();
+ let runtime = Rc::new(Runtime::new(fennel, secrets, &meta, &transitive));
+ self.runtime = Some(runtime.clone());
+
+ let lua = runtime.lua();
+ let rt_value = RuntimeHandle(runtime.clone())
+ .into_lua(lua)
+ .expect("install runtime on Lua VM");
- for job_id in &order {
- let job = pipeline
- .job(job_id)
- .expect("topo_order returned a job id not in pipeline");
+ self.transition(RunState::Active)?;
- self.runtime.enter_job(&job.id);
- let result = job.run_fn.call::<mlua::Value>(rt_value.clone());
- self.runtime.leave_job();
+ for (job_id, run_fn) in &run_fns {
+ self.runtime
+ .as_ref()
+ .expect("runtime installed")
+ .enter_job(job_id);
+ let result = run_fn.call::<mlua::Value>(rt_value.clone());
+ self.runtime
+ .as_ref()
+ .expect("runtime installed")
+ .leave_job();
if let Err(e) = result {
+ lua.remove_app_data::<Rc<Runtime>>();
self.transition(RunState::Failed)?;
return Err(Error::JobFailed {
- job: job.id.clone(),
+ job: job_id.clone(),
source: Box::new(e),
});
}
}
+ lua.remove_app_data::<Rc<Runtime>>();
self.transition(RunState::Complete)?;
Ok(())
}
@@ -320,7 +338,10 @@ impl Run {
/// most recent `execute` call. Empty if the job hasn't run or
/// produced no output.
pub fn outputs(&self, job_id: &str) -> Vec<ShOutput> {
- self.runtime.outputs(job_id)
+ self.runtime
+ .as_ref()
+ .map(|rt| rt.outputs(job_id))
+ .unwrap_or_default()
}
/// Transition the run from its current state to a new state.
@@ -387,44 +408,6 @@ impl Run {
}
}
-/// Build the source-ref outputs read by `(jobs name)` for source
-/// names. For v1, only `:quire/push` is exposed, derived from the run
-/// meta — `:sha`, `:ref`, `:pushed-at`. Branch/tag/etc are deferred.
-/// Pure Rust data; `lookup_input` serializes to Lua via serde.
-fn source_outputs(meta: &RunMeta) -> HashMap<String, InputOutputs> {
- let push = PushOutputs {
- sha: meta.sha.clone(),
- r#ref: meta.r#ref.clone(),
- pushed_at: meta.pushed_at,
- };
- let mut inputs = HashMap::new();
- inputs.insert("quire/push".to_string(), InputOutputs::Push(push));
- inputs
-}
-
-/// Materialize the per-job `(jobs name)` views from the pipeline's
-/// transitive-input map and the source outputs.
-///
-/// For each job J, the view holds every name reachable from J as a
-/// key. Source values populate the source entries; everything else
-/// sits as `None` so future job-to-job outputs can drop in without
-/// changing the lookup contract.
-fn build_inputs_views(
- transitive_inputs: &HashMap<String, std::collections::HashSet<String>>,
- sources: &HashMap<String, InputOutputs>,
-) -> HashMap<String, HashMap<String, Option<InputOutputs>>> {
- transitive_inputs
- .iter()
- .map(|(job_id, reachable)| {
- let view = reachable
- .iter()
- .map(|name| (name.clone(), sources.get(name).cloned()))
- .collect();
- (job_id.clone(), view)
- })
- .collect()
-}
-
/// Write a serializable value to a YAML file atomically (temp file + rename).
pub(crate) fn write_yaml<T: serde::Serialize>(path: &Path, value: &T) -> Result<()> {
let tmp_path = path.with_extension("yml.tmp");
@@ -607,7 +590,7 @@ mod tests {
base: PathBuf::from("/tmp/quire-test-runs/test.git"),
state: RunState::Pending,
id: uuid::Uuid::now_v7().to_string(),
- runtime: Rc::new(Runtime::default()),
+ runtime: None,
};
let result = run.transition(RunState::Active);
@@ -714,7 +697,7 @@ mod tests {
(ci.job :b [:a] (fn [{: sh}] (sh ["echo" "from-b"])))"#,
);
- run.execute(&pipeline, HashMap::new()).expect("execute");
+ run.execute(pipeline, HashMap::new()).expect("execute");
assert_eq!(run.state(), RunState::Complete);
@@ -744,7 +727,7 @@ mod tests {
);
let pipeline = load(&source);
- run.execute(&pipeline, HashMap::new()).expect("execute");
+ run.execute(pipeline, HashMap::new()).expect("execute");
let contents = fs_err::read_to_string(&log).expect("read log");
assert_eq!(contents, "a\nb\n");
@@ -763,7 +746,7 @@ mod tests {
);
let err = run
- .execute(&pipeline, HashMap::new())
+ .execute(pipeline, HashMap::new())
.expect_err("expected failure");
assert!(matches!(err, Error::JobFailed { ref job, .. } if job == "a"));
assert_eq!(run.state(), RunState::Failed);
@@ -787,7 +770,7 @@ mod tests {
(sh ["echo" push.sha push.ref]))))"#,
);
- run.execute(&pipeline, HashMap::new()).expect("execute");
+ run.execute(pipeline, HashMap::new()).expect("execute");
let outputs = run.outputs("grab");
assert_eq!(outputs.len(), 1);
@@ -811,7 +794,7 @@ mod tests {
(sh ["echo" push.sha]))))"#,
);
- run.execute(&pipeline, HashMap::new()).expect("execute");
+ run.execute(pipeline, HashMap::new()).expect("execute");
let outputs = run.outputs("b");
assert_eq!(outputs.len(), 1);
@@ -830,7 +813,7 @@ mod tests {
);
let err = run
- .execute(&pipeline, HashMap::new())
+ .execute(pipeline, HashMap::new())
.expect_err("expected failure");
match err {
Error::JobFailed { job, source } => {
@@ -859,7 +842,7 @@ mod tests {
);
let err = run
- .execute(&pipeline, HashMap::new())
+ .execute(pipeline, HashMap::new())
.expect_err("expected failure");
match err {
Error::JobFailed { source, .. } => {
@@ -885,7 +868,7 @@ mod tests {
);
let err = run
- .execute(&pipeline, HashMap::new())
+ .execute(pipeline, HashMap::new())
.expect_err("expected failure");
match err {
Error::JobFailed { source, .. } => {