mirror of
https://github.com/block/buzz.git
synced 2026-10-31 14:31:02 +00:00
test(desktop): enforce avconvert relay contract
Run the extensionless QuickTime conversion against a tiny checked-in H.264 fixture on every macOS test pass, and validate all AVFoundation output with the relay's production video validator before upload. Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -58,6 +58,7 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = ["N
|
||||
objc2-user-notifications = { version = "0.3.2", default-features = false, features = ["block2", "UNNotification", "UNNotificationContent", "UNNotificationRequest", "UNNotificationResponse", "UNNotificationSettings", "UNNotificationTrigger", "UNUserNotificationCenter"] }
|
||||
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }
|
||||
security-framework = { version = "3.7.0", features = ["OSX_10_15"] }
|
||||
buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" }
|
||||
window-vibrancy = "0.6"
|
||||
user-idle = { version = "0.6", default-features = false }
|
||||
plist = "1"
|
||||
@@ -153,6 +154,3 @@ tauri-utils = "2"
|
||||
# `test-util` enables tokio's paused-clock (`start_paused`) so the relay
|
||||
# admission gate tests can assert exact wait durations without real sleeps.
|
||||
tokio = { version = "1", features = ["test-util"] }
|
||||
# The relay's media validation, so the snapshot-sharing tests can prove the
|
||||
# full export → sanitize → relay-accept → import contract end to end.
|
||||
buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" }
|
||||
|
||||
@@ -486,6 +486,35 @@ fn prepare_avconvert_source(
|
||||
Ok((staged, true))
|
||||
}
|
||||
|
||||
/// Reject output that the relay cannot ingest before spending bandwidth.
|
||||
///
|
||||
/// `avconvert` can preserve metadata tracks from some source containers, while
|
||||
/// the relay deliberately accepts exactly one H.264 video track and at most
|
||||
/// one AAC audio track. Validate locally so those inputs fail with a useful
|
||||
/// conversion error instead of a later generic upload rejection.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn validate_avconvert_output(path: &std::path::Path) -> Result<(), String> {
|
||||
let config = buzz_media_pkg::MediaConfig {
|
||||
s3_endpoint: String::new(),
|
||||
s3_access_key: String::new(),
|
||||
s3_secret_key: String::new(),
|
||||
s3_bucket: String::new(),
|
||||
s3_region: "us-east-1".to_string(),
|
||||
s3_addressing_style: buzz_media_pkg::S3AddressingStyle::Path,
|
||||
max_image_bytes: 50 * 1024 * 1024,
|
||||
max_gif_bytes: 10 * 1024 * 1024,
|
||||
max_video_bytes: 500 * 1024 * 1024,
|
||||
max_file_bytes: 100 * 1024 * 1024,
|
||||
public_base_url: String::new(),
|
||||
upload_records_enabled: false,
|
||||
upload_ip_header: None,
|
||||
upload_port_header: None,
|
||||
};
|
||||
buzz_media_pkg::validation::validate_video_file(path, &config)
|
||||
.map(|_| ())
|
||||
.map_err(|error| format!("Video conversion produced an unsupported file: {error}"))
|
||||
}
|
||||
|
||||
/// Transcode a video with macOS's built-in AVFoundation converter.
|
||||
///
|
||||
/// Release builds do not bundle ffmpeg, and a stock macOS installation does
|
||||
@@ -536,6 +565,7 @@ fn transcode_with_avconvert(
|
||||
return Err("upload cancelled".to_string());
|
||||
}
|
||||
|
||||
validate_avconvert_output(&output)?;
|
||||
std::fs::read(&output).map_err(|error| format!("failed to read converted video: {error}"))
|
||||
})();
|
||||
|
||||
@@ -626,43 +656,22 @@ mod tests {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn test_avconvert_normalizes_configured_quicktime_fixture() {
|
||||
let Some(source) = std::env::var_os("BUZZ_TEST_QUICKTIME_PATH") else {
|
||||
eprintln!("skipping avconvert round-trip: BUZZ_TEST_QUICKTIME_PATH is unset");
|
||||
return;
|
||||
};
|
||||
let staged_source = std::env::temp_dir().join(format!(
|
||||
"buzz-avconvert-configured-test-{}",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
std::fs::copy(&source, &staged_source).expect("stage extensionless QuickTime fixture");
|
||||
fn test_avconvert_normalizes_extensionless_quicktime_fixture() {
|
||||
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/quicktime-screen-recording.mov");
|
||||
let staged_source =
|
||||
std::env::temp_dir().join(format!("buzz-avconvert-fixture-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::copy(fixture, &staged_source).expect("stage extensionless QuickTime fixture");
|
||||
|
||||
let video =
|
||||
transcode_with_avconvert(&staged_source, None).expect("convert QuickTime fixture");
|
||||
let _ = std::fs::remove_file(staged_source);
|
||||
let converted = std::env::temp_dir().join(format!(
|
||||
"buzz-avconvert-configured-output-{}.mp4",
|
||||
"buzz-avconvert-fixture-output-{}.mp4",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
std::fs::write(&converted, &video).expect("write converted fixture");
|
||||
let relay_config = buzz_media_pkg::MediaConfig {
|
||||
s3_endpoint: String::new(),
|
||||
s3_access_key: String::new(),
|
||||
s3_secret_key: String::new(),
|
||||
s3_bucket: String::new(),
|
||||
s3_region: "us-east-1".to_string(),
|
||||
s3_addressing_style: buzz_media_pkg::S3AddressingStyle::Path,
|
||||
max_image_bytes: 50 * 1024 * 1024,
|
||||
max_gif_bytes: 10 * 1024 * 1024,
|
||||
max_video_bytes: 500 * 1024 * 1024,
|
||||
max_file_bytes: 100 * 1024 * 1024,
|
||||
public_base_url: String::new(),
|
||||
upload_records_enabled: false,
|
||||
upload_ip_header: None,
|
||||
upload_port_header: None,
|
||||
};
|
||||
buzz_media_pkg::validation::validate_video_file(&converted, &relay_config)
|
||||
.expect("relay rejected avconvert output");
|
||||
validate_avconvert_output(&converted).expect("relay rejected avconvert output");
|
||||
let _ = std::fs::remove_file(converted);
|
||||
}
|
||||
|
||||
|
||||
54
desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift
vendored
Normal file
54
desktop/src-tauri/tests/fixtures/generate-quicktime-screen-recording.swift
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Generates the tiny H.264 QuickTime fixture beside this file.
|
||||
// Run: swift tests/fixtures/generate-quicktime-screen-recording.swift \
|
||||
// tests/fixtures/quicktime-screen-recording.mov
|
||||
import AVFoundation
|
||||
import CoreVideo
|
||||
import Foundation
|
||||
|
||||
let output = URL(fileURLWithPath: CommandLine.arguments[1])
|
||||
try? FileManager.default.removeItem(at: output)
|
||||
let writer = try AVAssetWriter(outputURL: output, fileType: .mov)
|
||||
let input = AVAssetWriterInput(
|
||||
mediaType: .video,
|
||||
outputSettings: [
|
||||
AVVideoCodecKey: AVVideoCodecType.h264,
|
||||
AVVideoWidthKey: 64,
|
||||
AVVideoHeightKey: 64,
|
||||
]
|
||||
)
|
||||
let adaptor = AVAssetWriterInputPixelBufferAdaptor(
|
||||
assetWriterInput: input,
|
||||
sourcePixelBufferAttributes: [
|
||||
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
|
||||
kCVPixelBufferWidthKey as String: 64,
|
||||
kCVPixelBufferHeightKey as String: 64,
|
||||
]
|
||||
)
|
||||
writer.add(input)
|
||||
guard writer.startWriting() else { fatalError("start failed: \(writer.error!)") }
|
||||
writer.startSession(atSourceTime: .zero)
|
||||
input.requestMediaDataWhenReady(on: DispatchQueue(label: "fixture")) {
|
||||
for frame in 0..<3 {
|
||||
while !input.isReadyForMoreMediaData { usleep(1_000) }
|
||||
var buffer: CVPixelBuffer?
|
||||
CVPixelBufferPoolCreatePixelBuffer(nil, adaptor.pixelBufferPool!, &buffer)
|
||||
guard let pixel = buffer else { fatalError("pixel buffer allocation failed") }
|
||||
CVPixelBufferLockBaseAddress(pixel, [])
|
||||
memset(
|
||||
CVPixelBufferGetBaseAddress(pixel),
|
||||
Int32(frame * 32),
|
||||
CVPixelBufferGetDataSize(pixel)
|
||||
)
|
||||
CVPixelBufferUnlockBaseAddress(pixel, [])
|
||||
guard adaptor.append(
|
||||
pixel,
|
||||
withPresentationTime: CMTime(value: CMTimeValue(frame), timescale: 30)
|
||||
) else { fatalError("append failed: \(writer.error!)") }
|
||||
}
|
||||
input.markAsFinished()
|
||||
writer.finishWriting {
|
||||
guard writer.status == .completed else { fatalError("finish failed: \(writer.error!)") }
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
dispatchMain()
|
||||
BIN
desktop/src-tauri/tests/fixtures/quicktime-screen-recording.mov
vendored
Normal file
BIN
desktop/src-tauri/tests/fixtures/quicktime-screen-recording.mov
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user