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)); +}