build: add differential tests against real dpkg-buildpackage
Add an automated differential test harness in build/mod.rs that builds the same source tree twice - once with real 'dpkg-buildpackage -S -I -i -nc -d --no-sign', once with the native pipeline - and compares all produced artifacts: - .dsc payload byte-for-byte, - .changes field-by-field (checksum lines of the .buildinfo itself excluded, as its content legitimately differs on machine-dependent fields), - .buildinfo structure (Installed-Build-Depends, Environment, Build-Date and Build-Tainted-By excluded).
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::DateTime;
|
||||
use regex::Regex;
|
||||
|
||||
use super::version::DebianVersion;
|
||||
|
||||
@@ -30,6 +31,9 @@ pub struct ChangelogEntry {
|
||||
/// Value for the `.changes` `Changes` field: header line, blank lines
|
||||
/// converted to `.`, body lines verbatim; without the trailer line.
|
||||
pub changes_field: String,
|
||||
/// Bug numbers collected from `(Closes: #NNN)` mentions in the body,
|
||||
/// sorted numerically and de-duplicated (like dpkg's `find_closes`).
|
||||
pub closes: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse the most recent entry of a Debian changelog file.
|
||||
@@ -163,6 +167,8 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
changes_field.push_str(&body_lines.join("\n"));
|
||||
}
|
||||
|
||||
let closes = find_closes(&body_lines);
|
||||
|
||||
Ok(ChangelogEntry {
|
||||
source,
|
||||
version,
|
||||
@@ -174,9 +180,67 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
date_raw,
|
||||
timestamp,
|
||||
changes_field,
|
||||
closes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract bug numbers from `(Closes: #NNN)` mentions in changelog body
|
||||
/// lines, sorted numerically and de-duplicated (a lenient port of dpkg's
|
||||
/// `find_closes`).
|
||||
fn find_closes(body_lines: &[String]) -> Option<String> {
|
||||
let re = Regex::new(r"(?i)\(closes:\s*([^)]*)\)").ok()?;
|
||||
let mut numbers: Vec<u64> = Vec::new();
|
||||
for line in body_lines {
|
||||
for capture in re.captures_iter(line) {
|
||||
if let Some(inner) = capture.get(1) {
|
||||
for token in inner.as_str().split(|c: char| !c.is_ascii_digit()) {
|
||||
if let Ok(n) = token.parse::<u64>() {
|
||||
numbers.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if numbers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
numbers.sort_unstable();
|
||||
numbers.dedup();
|
||||
Some(
|
||||
numbers
|
||||
.iter()
|
||||
.map(u64::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the version of the *previous* changelog entry (the second header
|
||||
/// in the file), or `None` when only one entry exists.
|
||||
pub fn parse_previous_version(path: &Path) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("failed to read changelog '{}': {}", path.display(), e))?;
|
||||
|
||||
let mut seen_first = false;
|
||||
for line in content.lines() {
|
||||
let line = line.trim_end();
|
||||
if looks_like_header(line) {
|
||||
if !seen_first {
|
||||
seen_first = true;
|
||||
continue;
|
||||
}
|
||||
let open = line.find('(').ok_or_else(|| {
|
||||
format!("invalid changelog header in '{}': {}", path.display(), line)
|
||||
})?;
|
||||
let close = line[open..]
|
||||
.find(')')
|
||||
.ok_or_else(|| format!("unbalanced parenthesis in changelog header '{}'", line))?;
|
||||
return Ok(Some(line[open + 1..open + close].to_string()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Heuristic check for a changelog entry header line
|
||||
/// (`name (version) dist; urgency=...`).
|
||||
fn looks_like_header(line: &str) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user