#!/usr/bin/perl use strict; use warnings; use Test::More; use File::Temp qw(tempfile); use File::Spec; # Overrides exit and silences the 'Usage' print during the 'do' call. BEGIN { *CORE::GLOBAL::exit = sub { }; } { local @ARGV = (); open my $oldout, ">&STDOUT"; open STDOUT, ">", File::Spec->devnull(); eval { do './vcx' }; open STDOUT, ">&", $oldout; } subtest 'Binary Patching: Shrink & Truncate' => sub { my ($fh_old, $old_path) = tempfile(); my ($fh_new, $new_path) = tempfile(); # 8KB of 'A' shrinking to 6 bytes print $fh_old "A" x 8192; close $fh_old; print $fh_new "SHRINK"; close $fh_new; my $patch = main::make_bin_patch($new_path, $old_path); ok(defined($patch), "Patch generated for shrinking file"); main::apply_bin_patch($old_path, $patch); is(-s $old_path, 6, "File size correctly truncated to 6 bytes"); open my $check, '<:raw', $old_path; my $content = <$check>; is($content, "SHRINK", "Content matches perfectly (no stale trailing data)"); close $check; }; subtest 'Binary Patching: Growth & Extension' => sub { my ($fh_old, $old_path) = tempfile(); my ($fh_new, $new_path) = tempfile(); print $fh_old "Tiny"; close $fh_old; # Create a 5KB string to cross a block boundary (4096) my $big_data = "EXTENDED" . ("." x 5000); print $fh_new $big_data; close $fh_new; my $patch = main::make_bin_patch($new_path, $old_path); main::apply_bin_patch($old_path, $patch); is(-s $old_path, 5008, "File size correctly extended"); open my $check, '<:raw', $old_path; my $result = do { local $/; <$check> }; is($result, $big_data, "Extended content matches perfectly"); close $check; }; subtest 'Binary Patching: Sparse Block Edits' => sub { my ($fh_old, $old_path) = tempfile(); my ($fh_new, $new_path) = tempfile(); # Create three 4KB blocks my $data = ("X" x 4096) . ("Y" x 4096) . ("Z" x 4096); print $fh_old $data; close $fh_old; # Modify only the middle block ('Y' block) substr($data, 5000, 10) = "MODIFIED!!"; print $fh_new $data; close $fh_new; my $patch = main::make_bin_patch($new_path, $old_path); # Header (8) + Block Header (12) + Block (4096) = ~4116 bytes ok(length($patch) < 4200, "Patch is efficient (only captured the changed block)"); main::apply_bin_patch($old_path, $patch); open my $check, '<:raw', $old_path; my $final = do { local $/; <$check> }; is(substr($final, 5000, 10), "MODIFIED!!", "Middle block update applied"); is(substr($final, 0, 10), "XXXXXXXXXX", "First block preserved"); is(substr($final, -10), "ZZZZZZZZZZ", "Last block preserved"); close $check; }; subtest 'Binary Patching: No Change Identity' => sub { my ($fh_old, $old_path) = tempfile(); my ($fh_new, $new_path) = tempfile(); my $data = "Same data" x 50; print $fh_old $data; close $fh_old; print $fh_new $data; close $fh_new; my $patch = main::make_bin_patch($new_path, $old_path); is($patch, undef, "No patch generated for identical files"); }; done_testing();