libcnb_test/build_config.rs
1use libcnb_data::buildpack::BuildpackId;
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use std::rc::Rc;
5
6pub use libcnb_package::CargoProfile;
7
8/// Configuration for a test.
9#[derive(Clone)]
10pub struct BuildConfig {
11 pub(crate) app_dir: PathBuf,
12 pub(crate) cargo_profile: CargoProfile,
13 pub(crate) target_triple: String,
14 pub(crate) builder_name: String,
15 pub(crate) buildpacks: Vec<BuildpackReference>,
16 pub(crate) env: HashMap<String, String>,
17 pub(crate) app_dir_preprocessor: Option<Rc<dyn Fn(PathBuf)>>,
18 pub(crate) expected_pack_result: PackResult,
19}
20
21impl BuildConfig {
22 /// Creates a new build configuration.
23 ///
24 /// If the `app_dir` parameter is a relative path, it is treated as relative to the Cargo
25 /// manifest directory ([`CARGO_MANIFEST_DIR`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates)),
26 /// i.e. the package's root directory.
27 ///
28 /// # Example
29 /// ```no_run
30 /// use libcnb_test::{BuildConfig, TestRunner};
31 ///
32 /// TestRunner::default().build(
33 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
34 /// |context| {
35 /// // ...
36 /// },
37 /// );
38 /// ```
39 pub fn new(builder_name: impl Into<String>, app_dir: impl AsRef<Path>) -> Self {
40 Self {
41 app_dir: PathBuf::from(app_dir.as_ref()),
42 cargo_profile: CargoProfile::Dev,
43 target_triple: String::from("x86_64-unknown-linux-musl"),
44 builder_name: builder_name.into(),
45 buildpacks: vec![BuildpackReference::CurrentCrate],
46 env: HashMap::new(),
47 app_dir_preprocessor: None,
48 expected_pack_result: PackResult::Success,
49 }
50 }
51
52 /// Sets the buildpacks (and their ordering) to use when building the app.
53 ///
54 /// Defaults to [`BuildpackReference::CurrentCrate`].
55 ///
56 /// # Example
57 /// ```no_run
58 /// use libcnb::data::buildpack_id;
59 /// use libcnb_test::{BuildConfig, BuildpackReference, TestRunner};
60 ///
61 /// TestRunner::default().build(
62 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").buildpacks([
63 /// BuildpackReference::CurrentCrate,
64 /// BuildpackReference::WorkspaceBuildpack(buildpack_id!("my-project/buildpack")),
65 /// BuildpackReference::Other(String::from("heroku/another-buildpack")),
66 /// ]),
67 /// |context| {
68 /// // ...
69 /// },
70 /// );
71 /// ```
72 pub fn buildpacks(&mut self, buildpacks: impl Into<Vec<BuildpackReference>>) -> &mut Self {
73 self.buildpacks = buildpacks.into();
74 self
75 }
76
77 /// Sets the Cargo profile used when compiling the buildpack.
78 ///
79 /// Defaults to [`CargoProfile::Dev`].
80 ///
81 /// # Example
82 /// ```no_run
83 /// use libcnb_test::{BuildConfig, CargoProfile, TestRunner};
84 ///
85 /// TestRunner::default().build(
86 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app")
87 /// .cargo_profile(CargoProfile::Release),
88 /// |context| {
89 /// // ...
90 /// },
91 /// );
92 /// ```
93 pub fn cargo_profile(&mut self, cargo_profile: CargoProfile) -> &mut Self {
94 self.cargo_profile = cargo_profile;
95 self
96 }
97
98 /// Sets the target triple used when compiling the buildpack.
99 ///
100 /// Defaults to `x86_64-unknown-linux-musl`.
101 ///
102 /// # Example
103 /// ```no_run
104 /// use libcnb_test::{BuildConfig, TestRunner};
105 ///
106 /// TestRunner::default().build(
107 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app")
108 /// .target_triple("x86_64-unknown-linux-musl"),
109 /// |context| {
110 /// // ...
111 /// },
112 /// );
113 /// ```
114 pub fn target_triple(&mut self, target_triple: impl Into<String>) -> &mut Self {
115 self.target_triple = target_triple.into();
116 self
117 }
118
119 /// Inserts or updates an environment variable mapping for the build process.
120 ///
121 /// Note: This does not set this environment variable for running containers, it's only
122 /// available during the build.
123 ///
124 /// # Example
125 /// ```no_run
126 /// use libcnb_test::{BuildConfig, TestRunner};
127 ///
128 /// TestRunner::default().build(
129 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app")
130 /// .env("ENV_VAR_ONE", "VALUE ONE")
131 /// .env("ENV_VAR_TWO", "SOME OTHER VALUE"),
132 /// |context| {
133 /// // ...
134 /// },
135 /// );
136 /// ```
137 pub fn env(&mut self, k: impl Into<String>, v: impl Into<String>) -> &mut Self {
138 self.env.insert(k.into(), v.into());
139 self
140 }
141
142 /// Adds or updates multiple environment variable mappings for the build process.
143 ///
144 /// Note: This does not set environment variables for running containers, they're only
145 /// available during the build.
146 ///
147 /// # Example
148 /// ```no_run
149 /// use libcnb_test::{BuildConfig, TestRunner};
150 ///
151 /// TestRunner::default().build(
152 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").envs([
153 /// ("ENV_VAR_ONE", "VALUE ONE"),
154 /// ("ENV_VAR_TWO", "SOME OTHER VALUE"),
155 /// ]),
156 /// |context| {
157 /// // ...
158 /// },
159 /// );
160 /// ```
161 pub fn envs<K: Into<String>, V: Into<String>, I: IntoIterator<Item = (K, V)>>(
162 &mut self,
163 envs: I,
164 ) -> &mut Self {
165 envs.into_iter().for_each(|(key, value)| {
166 self.env(key.into(), value.into());
167 });
168
169 self
170 }
171
172 /// Sets an app directory preprocessor function.
173 ///
174 /// It will be run after the app directory has been copied for the current integration test run,
175 /// the changes will not affect other integration test runs.
176 ///
177 /// Generally, we suggest using dedicated test fixtures. However, in some cases it is more
178 /// economical to slightly modify a fixture programmatically before a test instead.
179 ///
180 /// # Example
181 /// ```no_run
182 /// use libcnb_test::{BuildConfig, TestRunner};
183 ///
184 /// TestRunner::default().build(
185 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").app_dir_preprocessor(
186 /// |app_dir| {
187 /// std::fs::remove_file(app_dir.join("Procfile")).unwrap();
188 /// },
189 /// ),
190 /// |context| {
191 /// // ...
192 /// },
193 /// );
194 /// ```
195 pub fn app_dir_preprocessor<F: 'static + Fn(PathBuf)>(&mut self, f: F) -> &mut Self {
196 self.app_dir_preprocessor = Some(Rc::new(f));
197 self
198 }
199
200 /// Sets the app directory.
201 ///
202 /// The app directory is normally set in the [`BuildConfig::new`] call, but when sharing test
203 /// configuration, it might be necessary to change the app directory but keep everything else
204 /// the same.
205 ///
206 /// # Example
207 /// ```no_run
208 /// use libcnb_test::{BuildConfig, TestRunner};
209 ///
210 /// fn default_config() -> BuildConfig {
211 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app")
212 /// }
213 ///
214 /// TestRunner::default().build(
215 /// default_config().app_dir("tests/fixtures/a-different-app"),
216 /// |context| {
217 /// // ...
218 /// },
219 /// );
220 /// ```
221 pub fn app_dir<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
222 self.app_dir = path.into();
223 self
224 }
225
226 /// Set the expected `pack` command result.
227 ///
228 /// In some cases, users might want to explicitly test that a build fails and assert against
229 /// error output. When passed [`PackResult::Failure`], the test will fail if the pack build
230 /// succeeds and vice-versa.
231 ///
232 /// Defaults to [`PackResult::Success`].
233 ///
234 /// # Example
235 /// ```no_run
236 /// use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner};
237 ///
238 /// TestRunner::default().build(
239 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app")
240 /// .expected_pack_result(PackResult::Failure),
241 /// |context| {
242 /// assert_contains!(context.pack_stderr, "ERROR: Invalid Procfile!");
243 /// },
244 /// );
245 /// ```
246 pub fn expected_pack_result(&mut self, pack_result: PackResult) -> &mut Self {
247 self.expected_pack_result = pack_result;
248 self
249 }
250}
251
252/// References a Cloud Native Buildpack.
253#[derive(Debug, Clone, Eq, PartialEq)]
254pub enum BuildpackReference {
255 /// References the buildpack in the Rust Crate currently being tested.
256 ///
257 /// Is equivalent to `BuildpackReference::WorkspaceBuildpack(buildpack_id!("<buildpack ID of current crate"))`.
258 CurrentCrate,
259 /// References a libcnb.rs or composite buildpack within the Cargo workspace that needs to be packaged into a buildpack.
260 WorkspaceBuildpack(BuildpackId),
261 /// References another buildpack by id, local directory or tarball.
262 Other(String),
263}
264
265/// Result of a pack execution.
266#[derive(Debug, Clone, Eq, PartialEq)]
267pub enum PackResult {
268 /// Pack executed successfully.
269 Success,
270 /// Pack execution failed.
271 Failure,
272}