From dce67c823cc6e2a815e2cf4d99228dee89a694a4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 11 Dec 2024 20:38:07 -0500 Subject: [PATCH] Add `is_writeable` to the pipeline trait It has an implementation, and will be used to determine whether `sudo` is likely to be needed to install an extension. It needs to stringify file names, so move the `filename!` macro to the root crate and make it public inside the create. Add tests for it, though it requires annotation for the argument type. Sadly, test coverage cannot tell it has been tested, and [runtime-macros] looks a bit heavyweight for my taste. [runtime-macros]: https://crates.io/crates/runtime-macros --- Cargo.toml | 2 +- src/api/mod.rs | 12 +----------- src/lib.rs | 11 +++++++++++ src/pipeline/mod.rs | 17 ++++++++++++++++- src/pipeline/tests.rs | 11 +++++++++++ src/tests.rs | 10 +++++++++- 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32de55d..1d85fa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ regex = "1.11.1" semver = "1.0.23" serde = "1.0.215" serde_json = "1.0.133" +tempfile = "3.14.0" thiserror = "2.0.4" ureq = { version = "2.12.1", features = ["json"] } url = "2.5.4" @@ -33,6 +34,5 @@ zip = "2.2.1" [dev-dependencies] httpmock = "0.7.0" sha2 = "0.10.8" -tempfile = "3.14.0" temp-env = "0.3.6" assertables = "9.4.0" diff --git a/src/api/mod.rs b/src/api/mod.rs index c2a8a83..7437afe 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -21,16 +21,6 @@ use std::{ }; use url::Url; -/// Returns a string representation of the final segment of a Path. -macro_rules! filename { - ( $x:expr ) => {{ - $x.as_ref() - .file_name() - .unwrap_or(std::ffi::OsStr::new("UNKNOWN")) - .to_string_lossy() - }}; -} - macro_rules! copy_err { ($x:expr, $y:expr, $z:expr) => {{ Err(BuildError::File( @@ -128,7 +118,7 @@ impl Api { /// Unpack download `file` in directory `into` and return the path to the /// unpacked directory. pub fn unpack>(&self, into: P, file: P) -> Result { - info!(file:display = filename!(file); "unpacking"); + info!(file:display = crate::filename!(&file); "unpacking"); let zip = File::open(file)?; let mut archive = zip::ZipArchive::new(zip)?; archive.extract(&into)?; diff --git a/src/lib.rs b/src/lib.rs index 9f6fc24..0d0c638 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,5 +125,16 @@ impl> Builder

{ } } +/// Returns a string representation of the final segment of a Path. +macro_rules! filename { + ( $x:expr ) => {{ + AsRef::::as_ref($x) + .file_name() + .unwrap_or(std::ffi::OsStr::new("UNKNOWN")) + .to_string_lossy() + }}; +} +pub(crate) use filename; + #[cfg(test)] mod tests; diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 03d5835..29a21d2 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,7 +1,8 @@ //! Build Pipeline interface definition. use crate::error::BuildError; -use std::{path::Path, process::Command}; +use log::debug; +use std::{io::Write, path::Path, process::Command}; /// Defines the interface for build pipelines to configure, compile, and test /// PGXN distributions. @@ -30,6 +31,20 @@ pub(crate) trait Pipeline> { /// Returns the directory passed to [`new`]. fn dir(&self) -> &P; + /// Attempts to write a temporary file to `dir` and returns `true` on + /// success and `false` on failure. The temporary file will be deleted. + fn is_writeable>(&self, dir: D) -> bool { + debug!(dir:display = crate::filename!(&dir); "testing write access"); + match tempfile::Builder::new() + .prefix("pgxn-") + .suffix(".test") + .tempfile_in(dir) + { + Ok(f) => write!(&f, "ok").is_ok(), + Err(_) => false, + } + } + /// Run a command. Runs it with elevated privileges using `sudo` unless /// it's on Windows. fn run(&self, cmd: &str, args: I, sudo: bool) -> Result<(), BuildError> diff --git a/src/pipeline/tests.rs b/src/pipeline/tests.rs index 5763362..408fb57 100644 --- a/src/pipeline/tests.rs +++ b/src/pipeline/tests.rs @@ -91,3 +91,14 @@ fn run() -> Result<(), BuildError> { Ok(()) } + +#[test] +fn is_writeable() -> Result<(), BuildError> { + let tmp = tempdir()?; + + let pipe = TestPipeline::new(&tmp, false); + assert!(pipe.is_writeable(&tmp)); + assert!(!pipe.is_writeable(tmp.path().join(" nonesuch"))); + + Ok(()) +} diff --git a/src/tests.rs b/src/tests.rs index 33f98e0..e5b8279 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ use super::*; use serde_json::{json, Value}; -use std::{fs::File, io::Write, process::Command}; +use std::{fs::File, io::Write, path::PathBuf, process::Command}; use tempfile::tempdir; fn release_meta(pipeline: &str) -> Value { @@ -199,3 +199,11 @@ pub fn compile_mock(name: &str, dest: &str) { ) } } + +#[test] +fn filename() { + assert_eq!("string", filename!("string")); + assert_eq!("path", filename!(Path::new("path"))); + let pb = PathBuf::from(r"C:\windows\system32.dll"); + assert_eq!(r"C:\windows\system32.dll", filename!(&pb)); +}