summaryrefslogtreecommitdiffstats
path: root/vcx_patch.t
blob: d2b813fbf71cd5755476c661437425b84d3af7ba (plain)
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();