Skip to content

Commit 77dcfd4

Browse files
committed
Cluster records the runtime _strategy_, not the runtime
1 parent d4f1dbc commit 77dcfd4

File tree

7 files changed

+50
-51
lines changed

7 files changed

+50
-51
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ versions that are not supported upstream).
8585
use postgresfixture::prelude::*;
8686
for runtime in strategy::default().runtimes() {
8787
let data_dir = tempdir::TempDir::new("data")?;
88-
let cluster = Cluster::new(&data_dir, &runtime)?;
88+
let cluster = Cluster::new(&data_dir, runtime)?;
8989
cluster.start()?;
9090
assert_eq!(cluster.databases()?, vec!["postgres", "template0", "template1"]);
9191
let mut conn = cluster.connect("template1")?;

src/cluster.rs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,56 +25,54 @@ pub use error::ClusterError;
2525
/// stop, and destroy the cluster. There's no protection against concurrent
2626
/// changes to the cluster made by other processes, but the functions in the
2727
/// [`coordinate`][`crate::coordinate`] module may help.
28-
#[derive(Clone, Debug)]
2928
pub struct Cluster {
3029
/// The data directory of the cluster.
3130
///
3231
/// Corresponds to the `PGDATA` environment variable.
3332
datadir: PathBuf,
34-
/// The installation of PostgreSQL to use with this cluster.
35-
runtime: runtime::Runtime,
33+
/// How to select the PostgreSQL installation to use with this cluster.
34+
strategy: Box<dyn runtime::strategy::RuntimeStrategy>,
3635
}
3736

3837
impl Cluster {
3938
/// Represent a cluster at the given path.
40-
///
41-
/// This will use the given strategy to determine an appropriate runtime to
42-
/// use with the cluster in the given data directory, if it exists. If an
43-
/// appropriate runtime cannot be found, [`ClusterError::RuntimeNotFound`]
44-
/// will be returned.
4539
pub fn new<P: AsRef<Path>, S: runtime::strategy::RuntimeStrategy>(
4640
datadir: P,
47-
strategy: &S,
41+
strategy: S,
4842
) -> Result<Self, ClusterError> {
49-
let datadir = datadir.as_ref();
50-
let version = version(datadir)?;
51-
let runtime = match version {
52-
None => strategy
43+
Ok(Self {
44+
datadir: datadir.as_ref().to_owned(),
45+
strategy: Box::new(strategy),
46+
})
47+
}
48+
49+
/// Determine the runtime to use with this cluster.
50+
fn runtime(&self) -> Result<runtime::Runtime, ClusterError> {
51+
match version(self)? {
52+
None => self
53+
.strategy
5354
.fallback()
5455
.ok_or_else(|| ClusterError::RuntimeDefaultNotFound),
55-
Some(version) => strategy
56+
Some(version) => self
57+
.strategy
5658
.select(&version)
5759
.ok_or_else(|| ClusterError::RuntimeNotFound(version)),
58-
}?;
59-
Ok(Self {
60-
datadir: datadir.to_owned(),
61-
runtime,
62-
})
60+
}
6361
}
6462

65-
fn ctl(&self) -> Command {
66-
let mut command = self.runtime.execute("pg_ctl");
63+
fn ctl(&self) -> Result<Command, ClusterError> {
64+
let mut command = self.runtime()?.execute("pg_ctl");
6765
command.env("PGDATA", &self.datadir);
6866
command.env("PGHOST", &self.datadir);
69-
command
67+
Ok(command)
7068
}
7169

7270
/// Check if this cluster is running.
7371
///
7472
/// Tries to distinguish carefully between "definitely running", "definitely
7573
/// not running", and "don't know". The latter results in `ClusterError`.
7674
pub fn running(&self) -> Result<bool, ClusterError> {
77-
let output = self.ctl().arg("status").output()?;
75+
let output = self.ctl()?.arg("status").output()?;
7876
let code = match output.status.code() {
7977
// Killed by signal; return early.
8078
None => return Err(ClusterError::Other(output)),
@@ -83,11 +81,12 @@ impl Cluster {
8381
// More work required to decode what this means.
8482
Some(code) => code,
8583
};
84+
let runtime = self.runtime()?;
8685
// PostgreSQL has evolved to return different error codes in
8786
// later versions, so here we check for specific codes to avoid
8887
// masking errors from insufficient permissions or missing
8988
// executables, for example.
90-
let running = match self.runtime.version {
89+
let running = match runtime.version {
9190
// PostgreSQL 10.x and later.
9291
version::Version::Post10(_major, _minor) => {
9392
// PostgreSQL 10
@@ -162,7 +161,7 @@ impl Cluster {
162161
Some(running) => Ok(running),
163162
// TODO: Perhaps include the exit code from `pg_ctl status` in the
164163
// error message, and whatever it printed out.
165-
None => Err(ClusterError::UnsupportedVersion(self.runtime.version)),
164+
None => Err(ClusterError::UnsupportedVersion(runtime.version)),
166165
}
167166
}
168167

@@ -197,7 +196,7 @@ impl Cluster {
197196
// Create the cluster and report back that we did so.
198197
fs::create_dir_all(&self.datadir)?;
199198
#[allow(clippy::suspicious_command_arg_space)]
200-
self.ctl()
199+
self.ctl()?
201200
.arg("init")
202201
.arg("-s")
203202
.arg("-o")
@@ -236,7 +235,7 @@ impl Cluster {
236235
// postgres options:
237236
// -h <arg> -- host name; empty arg means Unix socket only.
238237
// -k -- socket directory.
239-
self.ctl()
238+
self.ctl()?
240239
.arg("start")
241240
.arg("-l")
242241
.arg(self.logfile())
@@ -266,7 +265,7 @@ impl Cluster {
266265
}
267266

268267
pub fn shell(&self, database: &str) -> Result<ExitStatus, ClusterError> {
269-
let mut command = self.runtime.execute("psql");
268+
let mut command = self.runtime()?.execute("psql");
270269
command.arg("--quiet");
271270
command.env("PGDATA", &self.datadir);
272271
command.env("PGHOST", &self.datadir);
@@ -280,7 +279,7 @@ impl Cluster {
280279
command: T,
281280
args: &[T],
282281
) -> Result<ExitStatus, ClusterError> {
283-
let mut command = self.runtime.command(command);
282+
let mut command = self.runtime()?.command(command);
284283
command.args(args);
285284
command.env("PGDATA", &self.datadir);
286285
command.env("PGHOST", &self.datadir);
@@ -338,7 +337,7 @@ impl Cluster {
338337
// pg_ctl options:
339338
// -w -- wait for shutdown to complete.
340339
// -m <mode> -- shutdown mode.
341-
self.ctl()
340+
self.ctl()?
342341
.arg("stop")
343342
.arg("-s")
344343
.arg("-w")

src/cluster/tests.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fn runtimes() -> Box<dyn Iterator<Item = Runtime>> {
1818
fn cluster_new() -> TestResult {
1919
for runtime in runtimes() {
2020
println!("{runtime:?}");
21-
let cluster = Cluster::new("some/path", &runtime)?;
21+
let cluster = Cluster::new("some/path", runtime)?;
2222
assert_eq!(Path::new("some/path"), cluster.datadir);
2323
assert!(!cluster.running()?);
2424
}
@@ -29,7 +29,7 @@ fn cluster_new() -> TestResult {
2929
fn cluster_does_not_exist() -> TestResult {
3030
for runtime in runtimes() {
3131
println!("{runtime:?}");
32-
let cluster = Cluster::new("some/path", &runtime)?;
32+
let cluster = Cluster::new("some/path", runtime)?;
3333
assert!(!exists(&cluster));
3434
}
3535
Ok(())
@@ -40,10 +40,10 @@ fn cluster_does_exist() -> TestResult {
4040
for runtime in runtimes() {
4141
println!("{runtime:?}");
4242
let data_dir = tempdir::TempDir::new("data")?;
43-
let cluster = Cluster::new(&data_dir, &runtime)?;
43+
let cluster = Cluster::new(&data_dir, runtime.clone())?;
4444
cluster.create()?;
4545
assert!(exists(&cluster));
46-
let cluster = Cluster::new(&data_dir, &runtime)?;
46+
let cluster = Cluster::new(&data_dir, runtime)?;
4747
assert!(exists(&cluster));
4848
}
4949
Ok(())
@@ -53,7 +53,7 @@ fn cluster_does_exist() -> TestResult {
5353
fn cluster_has_no_version_when_it_does_not_exist() -> TestResult {
5454
for runtime in runtimes() {
5555
println!("{runtime:?}");
56-
let cluster = Cluster::new("some/path", &runtime)?;
56+
let cluster = Cluster::new("some/path", runtime)?;
5757
assert!(matches!(version(&cluster), Ok(None)));
5858
}
5959
Ok(())
@@ -69,7 +69,7 @@ fn cluster_has_version_when_it_does_exist() -> TestResult {
6969
let pg_version: PartialVersion = runtime.version.into();
7070
let pg_version = pg_version.widened(); // e.g. 9.6.5 -> 9.6 or 14.3 -> 14.
7171
std::fs::write(&version_file, format!("{pg_version}\n"))?;
72-
let cluster = Cluster::new(&data_dir, &runtime)?;
72+
let cluster = Cluster::new(&data_dir, runtime)?;
7373
assert!(matches!(version(&cluster), Ok(Some(_))));
7474
}
7575
Ok(())
@@ -80,7 +80,7 @@ fn cluster_has_pid_file() -> TestResult {
8080
let data_dir = PathBuf::from("/some/where");
8181
for runtime in runtimes() {
8282
println!("{runtime:?}");
83-
let cluster = Cluster::new(&data_dir, &runtime)?;
83+
let cluster = Cluster::new(&data_dir, runtime)?;
8484
assert_eq!(
8585
PathBuf::from("/some/where/postmaster.pid"),
8686
cluster.pidfile()
@@ -94,7 +94,7 @@ fn cluster_has_log_file() -> TestResult {
9494
let data_dir = PathBuf::from("/some/where");
9595
for runtime in runtimes() {
9696
println!("{runtime:?}");
97-
let cluster = Cluster::new(&data_dir, &runtime)?;
97+
let cluster = Cluster::new(&data_dir, runtime)?;
9898
assert_eq!(
9999
PathBuf::from("/some/where/postmaster.log"),
100100
cluster.logfile()
@@ -108,7 +108,7 @@ fn cluster_create_creates_cluster() -> TestResult {
108108
for runtime in runtimes() {
109109
println!("{runtime:?}");
110110
let data_dir = tempdir::TempDir::new("data")?;
111-
let cluster = Cluster::new(&data_dir, &runtime)?;
111+
let cluster = Cluster::new(&data_dir, runtime)?;
112112
assert!(!exists(&cluster));
113113
assert!(cluster.create()?);
114114
assert!(exists(&cluster));
@@ -121,7 +121,7 @@ fn cluster_create_creates_cluster_with_neutral_locale_and_timezone() -> TestResu
121121
for runtime in runtimes() {
122122
println!("{runtime:?}");
123123
let data_dir = tempdir::TempDir::new("data")?;
124-
let cluster = Cluster::new(&data_dir, &runtime)?;
124+
let cluster = Cluster::new(&data_dir, runtime.clone())?;
125125
cluster.start()?;
126126
let mut conn = cluster.connect("postgres")?;
127127
let result = conn.query("SHOW ALL", &[])?;
@@ -190,7 +190,7 @@ fn cluster_create_does_nothing_when_it_already_exists() -> TestResult {
190190
for runtime in runtimes() {
191191
println!("{runtime:?}");
192192
let data_dir = tempdir::TempDir::new("data")?;
193-
let cluster = Cluster::new(&data_dir, &runtime)?;
193+
let cluster = Cluster::new(&data_dir, runtime)?;
194194
assert!(!exists(&cluster));
195195
assert!(cluster.create()?);
196196
assert!(exists(&cluster));
@@ -204,7 +204,7 @@ fn cluster_start_stop_starts_and_stops_cluster() -> TestResult {
204204
for runtime in runtimes() {
205205
println!("{runtime:?}");
206206
let data_dir = tempdir::TempDir::new("data")?;
207-
let cluster = Cluster::new(&data_dir, &runtime)?;
207+
let cluster = Cluster::new(&data_dir, runtime)?;
208208
cluster.create()?;
209209
assert!(!cluster.running()?);
210210
cluster.start()?;
@@ -220,7 +220,7 @@ fn cluster_destroy_stops_and_removes_cluster() -> TestResult {
220220
for runtime in runtimes() {
221221
println!("{runtime:?}");
222222
let data_dir = tempdir::TempDir::new("data")?;
223-
let cluster = Cluster::new(&data_dir, &runtime)?;
223+
let cluster = Cluster::new(&data_dir, runtime)?;
224224
cluster.create()?;
225225
cluster.start()?;
226226
assert!(exists(&cluster));
@@ -235,7 +235,7 @@ fn cluster_connect_connects() -> TestResult {
235235
for runtime in runtimes() {
236236
println!("{runtime:?}");
237237
let data_dir = tempdir::TempDir::new("data")?;
238-
let cluster = Cluster::new(&data_dir, &runtime)?;
238+
let cluster = Cluster::new(&data_dir, runtime)?;
239239
cluster.start()?;
240240
cluster.connect("template1")?;
241241
cluster.destroy()?;
@@ -248,7 +248,7 @@ fn cluster_databases_returns_vec_of_database_names() -> TestResult {
248248
for runtime in runtimes() {
249249
println!("{runtime:?}");
250250
let data_dir = tempdir::TempDir::new("data")?;
251-
let cluster = Cluster::new(&data_dir, &runtime)?;
251+
let cluster = Cluster::new(&data_dir, runtime)?;
252252
cluster.start()?;
253253

254254
let expected: HashSet<String> = ["postgres", "template0", "template1"]
@@ -270,7 +270,7 @@ fn cluster_databases_with_non_plain_names_can_be_created_and_dropped() -> TestRe
270270
for runtime in runtimes() {
271271
println!("{runtime:?}");
272272
let data_dir = tempdir::TempDir::new("data")?;
273-
let cluster = Cluster::new(&data_dir, &runtime)?;
273+
let cluster = Cluster::new(&data_dir, runtime)?;
274274
cluster.start()?;
275275
cluster.createdb("foo-bar")?;
276276
cluster.createdb("Foo-BAR")?;

src/coordinate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! let cluster_dir = tempdir::TempDir::new("cluster")?;
1010
//! let data_dir = cluster_dir.path().join("data");
1111
//! let runtime = strategy::default();
12-
//! let cluster = Cluster::new(&data_dir, &runtime)?;
12+
//! let cluster = Cluster::new(&data_dir, runtime)?;
1313
//! let lock_file = cluster_dir.path().join("lock");
1414
//! let lock = lock::UnlockedFile::try_from(lock_file.as_path())?;
1515
//! assert!(coordinate::run_and_stop(&cluster, lock, cluster::exists)?);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
//! use postgresfixture::prelude::*;
1717
//! for runtime in strategy::default().runtimes() {
1818
//! let data_dir = tempdir::TempDir::new("data")?;
19-
//! let cluster = Cluster::new(&data_dir, &runtime)?;
19+
//! let cluster = Cluster::new(&data_dir, runtime)?;
2020
//! cluster.start()?;
2121
//! assert_eq!(cluster.databases()?, vec!["postgres", "template0", "template1"]);
2222
//! let mut conn = cluster.connect("template1")?;

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ where
128128
.with_section(|| lock_uuid.to_string().header("UUID for lock file:"))?;
129129

130130
let strategy = runtime::strategy::default();
131-
let cluster = cluster::Cluster::new(&database_dir, &strategy)?;
131+
let cluster = cluster::Cluster::new(&database_dir, strategy)?;
132132

133133
let runner = if destroy {
134134
coordinate::run_and_destroy

src/runtime/strategy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub type Runtimes<'a> = Box<dyn Iterator<Item = Runtime> + 'a>;
1818
///
1919
/// This trait models those questions, and provides default implementations for
2020
/// #2 and #3.
21-
pub trait RuntimeStrategy {
21+
pub trait RuntimeStrategy: std::panic::RefUnwindSafe + 'static {
2222
/// Find all runtimes that this strategy knows about.
2323
fn runtimes(&self) -> Runtimes;
2424

0 commit comments

Comments
 (0)