1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
#!/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();
|