commit f8e02d9730b6fd44e7b2cfb5104b610d921479ef
parent aa49980281add0cef16b2c1efda3eef2b9e5ae9c
Author: Antoine A <>
Date: Thu, 30 Jul 2026 12:38:56 +0200
harness: add compact cmd
Diffstat:
3 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3583,6 +3583,7 @@ name = "testbench"
version = "1.5.0"
dependencies = [
"anyhow",
+ "aws-lc-rs",
"axum",
"clap",
"globset",
diff --git a/testbench/Cargo.toml b/testbench/Cargo.toml
@@ -19,6 +19,7 @@ taler-test-utils.workspace = true
sqlx.workspace = true
http-client.workspace = true
axum.workspace = true
+aws-lc-rs.workspace = true
libeufin-bank = { path = "../libeufin-bank", features = ["test-utils"] }
libeufin-nexus = { path = "../libeufin-nexus" }
libeufin-ebisync = { path = "../libeufin-ebisync" }
diff --git a/testbench/src/main.rs b/testbench/src/main.rs
@@ -17,9 +17,15 @@
* <http://www.gnu.org/licenses/>
*/
-use std::{fmt::Display, path::PathBuf, str::FromStr};
+use std::{
+ collections::HashSet,
+ fmt::Display,
+ path::{Path, PathBuf},
+ str::FromStr,
+};
use anyhow::bail;
+use aws_lc_rs::digest::SHA256;
use clap::{Parser, ValueEnum};
use globset::{Glob, GlobSetBuilder};
use jiff::Timestamp;
@@ -105,6 +111,7 @@ pub enum NexusCmd {
path: PathBuf,
glob: Vec<Glob>,
},
+ Compact,
}
#[derive(Parser)]
@@ -125,6 +132,7 @@ pub enum SyncCmd {
raw_args: Vec<String>,
},
Serve,
+ Compact,
}
fn step(name: impl Display) {
@@ -289,7 +297,8 @@ async fn main() -> anyhow::Result<()> {
msg(format!("Init {name}"));
run("dbinit").await;
- let prompt = format!("{:?} {} {name}", cmd.component, cmd.platform);
+ let platform = cmd.platform;
+ let prompt = format!("{:?} {platform} {name}", cmd.component);
match cmd.component {
Component::Nexus => {
@@ -304,6 +313,9 @@ async fn main() -> anyhow::Result<()> {
NexusCmd::ResetDb => {
run("dbinit -r").await;
}
+ NexusCmd::Compact => {
+ compact(format!("testbench/test/{platform}"));
+ }
NexusCmd::Fetch { raw_args } => {
run(&format!("ebics-fetch {ebics_log} {}", raw_args.join(" "))).await;
}
@@ -366,6 +378,9 @@ async fn main() -> anyhow::Result<()> {
auto_setup().await;
if let Some(cmd) = repl.read_line(&prompt, "") {
match cmd {
+ SyncCmd::Compact => {
+ compact(format!("testbench/test/{platform}"));
+ }
SyncCmd::Setup => {
run(&format!("setup {ebics_log}")).await;
}
@@ -397,3 +412,89 @@ async fn main() -> anyhow::Result<()> {
}
Ok(())
}
+
+/// Remove duplicated files and empty dir
+fn compact(p: String) {
+ /// Announce and recursively remove file or directory
+ fn remove(p: &Path, reason: &str) {
+ println!("rm {} {}", reason, p.display());
+ if p.is_dir() {
+ std::fs::remove_dir_all(p).unwrap();
+ } else {
+ std::fs::remove_file(p).unwrap()
+ }
+ }
+
+ /// Collect directory entries into a Vec upfront
+ /// and avoids surprises from mutating a directory while iterating it).
+ fn read_dir_paths(dir: &Path) -> Vec<PathBuf> {
+ let mut v = Vec::new();
+ for entry in std::fs::read_dir(dir).unwrap() {
+ v.push(entry.unwrap().path());
+ }
+ v
+ }
+
+ fn is_dir_empty(dir: &Path) -> bool {
+ std::fs::read_dir(dir).unwrap().next().is_none()
+ }
+
+ /// Delete file if another file has the same (whitespace-stripped) content
+ fn rm_if_similar(p: &Path, content: &str, content_hashes: &mut HashSet<Vec<u8>>) {
+ let normalized: String = content.chars().filter(|c| c.is_whitespace()).collect();
+
+ let digest = aws_lc_rs::digest::digest(&SHA256, normalized.as_bytes());
+
+ if content_hashes.contains(digest.as_ref()) {
+ remove(p, "similar");
+ } else {
+ content_hashes.insert(digest.as_ref().to_vec());
+ }
+ }
+
+ fn file_name_str(p: &Path) -> String {
+ p.file_name()
+ .map(|n| n.to_string_lossy().into_owned())
+ .unwrap_or_default()
+ }
+
+ let mut content_hashes: HashSet<Vec<u8>> = HashSet::new();
+
+ for date in read_dir_paths(p.as_ref()) {
+ if !date.is_dir() {
+ continue;
+ }
+
+ for request in read_dir_paths(&date) {
+ let payload_file_path = request.join("payload.xml");
+ let payload_dir_path = request.join("payload");
+ let request_name = file_name_str(&request);
+
+ if payload_file_path.exists() {
+ let content = std::fs::read_to_string(&payload_file_path).unwrap();
+
+ if request_name.contains("HAC") && !content.contains("ORDER_HAC_FINAL_NEG") {
+ remove(&request, "simple hac");
+ } else if request_name.contains("HAA") && !content.contains("<Service>") {
+ remove(&request, "empty haa");
+ } else if request_name.contains("wssparam") {
+ remove(&request, "wssparam");
+ } else {
+ rm_if_similar(&payload_file_path, &content, &mut content_hashes);
+ }
+ } else if payload_dir_path.exists() {
+ for file in read_dir_paths(&payload_dir_path) {
+ let content = std::fs::read_to_string(&file).unwrap();
+ rm_if_similar(&file, &content, &mut content_hashes);
+ }
+ }
+ if request.exists() && !payload_file_path.exists() && !payload_dir_path.exists() {
+ remove(&request, "empty request");
+ }
+ }
+
+ if is_dir_empty(&date) {
+ remove(&date, "empty dir");
+ }
+ }
+}