-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
It takes a path to `pg_config`, executes it, and stores all the configuration values. A `get` method provides access to configurations (using a lowercase key name), while `iter` and `into_iter` allow iteration over all the configurations. The module is crate-only for now, and not yet used, but will be soon. In order to support tests on multiple OSes, add `mocks/pg_config.rs` and `mocks/exit_err.rs`. The PgConfig tests use `rustc` to compile apps for the current OS that mock the output of `pg_config` and that exit with an error. This eliminates the need for a bunch of conditional code to build shell scripts or batch files; we're testing Rust, so Rust is available, so just use it to build the apps and ensure consistent behavior. Tweak the handling of Command errors by stringifying a Command before creating the error object. This avoids a bunch of ownership issues. The code does the work only in conditional blocks that execute when an error is needed, so the overhead is minimal.
- Loading branch information
Showing
9 changed files
with
223 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,8 +33,8 @@ jobs: | |
uses: dtolnay/rust-toolchain@stable | ||
- name: Run pre-commit | ||
uses: pre-commit/[email protected] | ||
- uses: actions-rust-lang/audit@v1 | ||
name: Audit Rust Dependencies | ||
- name: Audit Rust Dependencies | ||
uses: actions-rust-lang/audit@v1 | ||
- name: Generate Coverage | ||
run: make cover RUST_BACKTRACE=1 | ||
- name: Publish Coverage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Simple app that returns an error. | ||
fn main() { | ||
let args: Vec<String> = std::env::args().collect(); | ||
println!("DED: {}", &args[1..].join(" ")); | ||
std::process::exit(2) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Mock pg_config. | ||
|
||
fn main() { | ||
println!("BINDIR = /opt/data/pgsql-17.2/bin"); | ||
println!("MANDIR = /opt/data/pgsql-17.2/share/man"); | ||
println!("PGXS = /opt/data/pgsql-17.2/lib/pgxs/src/makefiles/pgxs.mk"); | ||
println!("CFLAGS_SL = "); | ||
println!("LIBS = -lpgcommon -lpgport -lxml2 -lssl -lcrypto -lz -lreadline -lm "); | ||
println!("VERSION = PostgreSQL 17.2"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use std::{ | ||
collections::{self, HashMap}, | ||
io::{BufRead, BufReader}, | ||
path::Path, | ||
process::Command, | ||
}; | ||
|
||
use crate::error::BuildError; | ||
|
||
pub(crate) struct PgConfig(HashMap<String, String>); | ||
|
||
impl PgConfig { | ||
/// Executes `pg_config`, parses the output, and returns a `PgConfig` | ||
/// containing its key/value pairs. | ||
pub fn new<P: AsRef<Path>>(pg_config: P) -> Result<Self, BuildError> { | ||
// Execute pg_config. | ||
let mut cmd = Command::new(pg_config.as_ref().as_os_str()); | ||
|
||
let out = cmd | ||
.output() | ||
.map_err(|e| BuildError::Command(format!("{:?}", cmd), e.kind().to_string()))?; | ||
if !out.status.success() { | ||
return Err(BuildError::Command( | ||
format!("{:?}", cmd), | ||
String::from_utf8_lossy(&out.stdout).to_string(), | ||
)); | ||
} | ||
|
||
// Parse each line, splitting on " = ". | ||
let reader = BufReader::new(out.stdout.as_slice()); | ||
let mut cfg = HashMap::new(); | ||
for line in reader.lines().map_while(Result::ok) { | ||
let mut split = line.splitn(2, " = "); | ||
if let Some(key) = split.nth(0) { | ||
if let Some(val) = split.last() { | ||
cfg.insert(key.to_ascii_lowercase(), val.to_string()); | ||
} | ||
} | ||
} | ||
|
||
Ok(PgConfig(cfg)) | ||
} | ||
|
||
/// Returns the `pg_config` value for `cfg`, which should be a lowercase | ||
/// string. | ||
pub fn get(&mut self, cfg: &str) -> Option<&str> { | ||
match self.0.get(cfg) { | ||
Some(c) => Some(c.as_str()), | ||
None => None, | ||
} | ||
} | ||
|
||
/// An iterator visiting all `pg_config` key-value pairs in arbitrary | ||
/// order. The iterator element type is `(&'a str, &'a str)`. | ||
pub fn iter(&self) -> collections::hash_map::Iter<'_, String, String> { | ||
self.0.iter() | ||
} | ||
} | ||
|
||
impl<'h> IntoIterator for &'h PgConfig { | ||
type Item = <&'h HashMap<String, String> as IntoIterator>::Item; | ||
type IntoIter = <&'h HashMap<String, String> as IntoIterator>::IntoIter; | ||
|
||
/// Convert into an iterator visiting all `pg_config` key-value pairs in | ||
/// arbitrary order. The iterator element type is `(&'a str, &'a str)`. | ||
fn into_iter(self) -> Self::IntoIter { | ||
self.0.iter() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
use super::*; | ||
use assertables::*; | ||
use tempfile::tempdir; | ||
|
||
#[test] | ||
fn pg_config() -> Result<(), BuildError> { | ||
// Build a mock pg_config. | ||
let tmp = tempdir()?; | ||
let path = tmp.path().join("pg_config").display().to_string(); | ||
compile_mock("pg_config", &path); | ||
|
||
let exp = HashMap::from([ | ||
("bindir".to_string(), "/opt/data/pgsql-17.2/bin".to_string()), | ||
( | ||
"mandir".to_string(), | ||
"/opt/data/pgsql-17.2/share/man".to_string(), | ||
), | ||
( | ||
"pgxs".to_string(), | ||
"/opt/data/pgsql-17.2/lib/pgxs/src/makefiles/pgxs.mk".to_string(), | ||
), | ||
("cflags_sl".to_string(), "".to_string()), | ||
( | ||
"libs".to_string(), | ||
"-lpgcommon -lpgport -lxml2 -lssl -lcrypto -lz -lreadline -lm ".to_string(), | ||
), | ||
("version".to_string(), "PostgreSQL 17.2".to_string()), | ||
]); | ||
|
||
// Parse its output. | ||
let mut cfg = PgConfig::new(&path)?; | ||
assert_eq!(&exp, &cfg.0); | ||
|
||
// Get lowercase. | ||
assert_eq!( | ||
cfg.get("bindir"), | ||
Some("/opt/data/pgsql-17.2/bin"), | ||
"bindir" | ||
); | ||
assert_eq!( | ||
cfg.get("mandir"), | ||
Some("/opt/data/pgsql-17.2/share/man"), | ||
"mandir" | ||
); | ||
assert_eq!( | ||
cfg.get("pgxs"), | ||
Some("/opt/data/pgsql-17.2/lib/pgxs/src/makefiles/pgxs.mk"), | ||
"pgxs", | ||
); | ||
assert_eq!(cfg.get("cflags_sl"), Some("")); | ||
assert_eq!( | ||
cfg.get("libs"), | ||
Some("-lpgcommon -lpgport -lxml2 -lssl -lcrypto -lz -lreadline -lm "), | ||
"libs", | ||
); | ||
assert_eq!(cfg.get("version"), Some("PostgreSQL 17.2"), "version"); | ||
|
||
// Uppercase and unknown keys ignored. | ||
for name in [ | ||
"BINDIR", | ||
"MANDIR", | ||
"PGXS", | ||
"CFLAGS_SL", | ||
"LIBS", | ||
"VERSION", | ||
"nonesuch", | ||
] { | ||
assert_eq!(cfg.get(name), None, "{name}"); | ||
} | ||
|
||
// Test iter. | ||
let mut all = HashMap::new(); | ||
for (k, v) in cfg.iter() { | ||
all.insert(k.to_string(), v.to_string()); | ||
} | ||
assert_eq!(&exp, &all); | ||
|
||
// Test into_iter. | ||
let mut all = HashMap::new(); | ||
for (k, v) in &cfg { | ||
all.insert(k.to_string(), v.to_string()); | ||
} | ||
assert_eq!(&exp, &all); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn pg_config_err() { | ||
// Build a mock pg_config that exits with an error. | ||
let tmp = tempdir().unwrap(); | ||
let path = tmp.path().join("exit_err").display().to_string(); | ||
compile_mock("exit_err", &path); | ||
|
||
// Get the error. | ||
match PgConfig::new(&path) { | ||
Ok(_) => panic!("exit_err unexpectedly succeeded"), | ||
Err(e) => { | ||
assert_starts_with!(e.to_string(), "executing"); | ||
assert_ends_with!(e.to_string(), " DED: \n"); | ||
} | ||
} | ||
|
||
// Try executing a nonexistent file. | ||
let path = tmp.path().join("nonesuch").display().to_string(); | ||
match PgConfig::new(&path) { | ||
Ok(_) => panic!("nonesuch unexpectedly succeeded"), | ||
Err(e) => { | ||
assert_starts_with!(e.to_string(), "executing"); | ||
assert_ends_with!(e.to_string(), "nonesuch\"`: entity not found"); | ||
} | ||
} | ||
} | ||
|
||
fn compile_mock(name: &str, dest: &str) { | ||
let src = Path::new(env!("CARGO_MANIFEST_DIR")) | ||
.join("mocks") | ||
.join(format!("{name}.rs")) | ||
.display() | ||
.to_string(); | ||
Command::new("rustc") | ||
.args([&src, "-o", dest]) | ||
.output() | ||
.unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters