summaryrefslogtreecommitdiffstats
path: root/test_vcx.t
blob: 2467819417e56e2157ef4b40d9d67087e5bbaeec (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use strict;
use warnings;
use Test::More;
use File::Path qw(remove_tree make_path);
use File::Spec;
use Cwd;
use File::Glob qw(:bsd_glob);

use constant ROOT	  => '.vcx';
use constant HEAD	  => ROOT . '/head';  # Current commit ID
use constant OBJ_DIR  => ROOT . '/obj';   # Latest version of a file
use constant REV_DIR  => ROOT . '/rev';   # Commits

# Staging area
use constant TMP_DIR	=> ROOT . '/index';
use constant TMP_TREE   => TMP_DIR . '/tree';

# Setup Sandbox
my $sandbox = "sandbox";
remove_tree($sandbox) if -d $sandbox;
make_path($sandbox);

my $orig_dir = getcwd();
chdir($sandbox) or die "Cant enter sandbox: $!";

my $cmd = File::Spec->catfile($orig_dir, "vcx");

# Test 'init'
ok(system("perl $cmd init > /dev/null") == 0, "Init command ran successfully");
ok(-d ROOT, "ROOT directory created");
ok(-e HEAD, "Head file created");
ok(-d OBJ_DIR, "OBJ_DIR directory created");
ok(-d REV_DIR, "REV_DIR directory created");

# Test 'add'
open my $fh, '>', "test.txt" or die $!;
print $fh "Hello, world!";
close $fh;

ok(system("perl $cmd add test.txt > /dev/null") == 0, 
	"Add command ran successfully");
ok(-d TMP_TREE, "Staging tree created");

# Test 'commit -m'
ok(system("perl $cmd commit -m 'Initial commit' > /dev/null") == 0, 
	"Commit with -m ran successfully");
my $rev1 = File::Spec->catdir(REV_DIR, "0000001");
ok(-d $rev1, "Revision 0000001 created");

# Check the message content
my $msg_file = File::Spec->catfile($rev1, "message");
ok(-f $msg_file, "Message file exists in revision 1");

open my $msg_fh, '<', $msg_file or die $!;
my $saved_msg = <$msg_fh>; chomp $saved_msg; close $msg_fh;
is($saved_msg, "Initial commit", "Commit message matches the input");

# Test 'commit -am' (Auto-staging)
open my $fh2, '>>', "test.txt" or die $!;
print $fh2 "\nMore content";
close $fh2;

ok(system("perl $cmd commit -am 'Second commit' > /dev/null") == 0, 
	"Commit -am (auto-staging) ran successfully");

# Check if the object store updated
opendir(my $dh, OBJ_DIR) or die $!;
my @objs = grep { /[a-f0-9]{40}/ } readdir($dh);
closedir($dh);

ok(scalar @objs >= 1, "Objects exist in store");

# Shuffle subdirectories
make_path("dirA/dirB");
write_file("dirA/dirB/shuffle.txt", "Same Content");
system("perl $cmd add dirA/dirB/shuffle.txt > /dev/null");
system("perl $cmd commit -m 'Shuffle part 1' > /dev/null");

# Move and delete
make_path("dirC");
rename("dirA/dirB/shuffle.txt", "dirC/shuffle.txt");
remove_tree("dirA");

# Commit the move
system("perl $cmd commit -am 'Shuffle part 2' > /dev/null");

# Verify Revision 4's tree structure
my $head = read_file(HEAD);
my ($tree_ptr) = bsd_glob(File::Spec->catfile(REV_DIR, $head, "tree-*"));
my ($tree_hash) = $tree_ptr =~ /tree-([a-f0-9]{40})$/;
my $actual_tree_path = File::Spec->catdir(OBJ_DIR, $tree_hash);

# Check for new path
ok(-l File::Spec->catfile($actual_tree_path, "dirC/shuffle.txt"), 
	"New path dirC/shuffle.txt exists in object store");

# Check for deleted path (Should NOT exist)
ok(!-d File::Spec->catdir($actual_tree_path, "dirA"), 
	"Deleted directory removed from tree snapshot");

# Verify the symlink still points to the correct content blob
my $link_target = readlink(File::Spec->catfile($actual_tree_path, "dirC/shuffle.txt"));
like($link_target, qr/obj\/[a-f0-9]{40}/, "Symlink in tree points to a valid object blob");

## Symlink-to-symlink
write_file("target.txt", "Final Destination");
symlink("target.txt", "link_a");
symlink("link_a", "link_b");

system("perl $cmd add link_b > /dev/null");
system("perl $cmd commit -m 'Double link' > /dev/null");

$head = read_file(HEAD);
($tree_ptr) = bsd_glob(File::Spec->catfile(REV_DIR, $head, "tree-*"));
my $staged_link = File::Spec->catfile(OBJ_DIR, $tree_ptr =~ s/.*tree-//r, "link_b");

is(readlink($staged_link), "link_a", "Symlink-to-symlink preserved literal target");

## Empty file
write_file("empty.txt", "");
system("perl $cmd add empty.txt > /dev/null");
system("perl $cmd commit -m 'Empty file' > /dev/null");

ok(-e File::Spec->catfile(OBJ_DIR, "da39a3ee5e6b4b0d3255bfef95601890afd80709"), 
	"Empty file object created (da39a3...)");

# The overwrite
write_file("repeat.txt", "Version A");
system("perl $cmd add repeat.txt > /dev/null");
system("perl $cmd commit -m 'State A' > /dev/null");
my $tree_v1 = (bsd_glob(File::Spec->catfile(REV_DIR, read_file(HEAD), "tree-*")))[0];

write_file("repeat.txt", "Version B");
system("perl $cmd add repeat.txt > /dev/null");
system("perl $cmd commit -m 'State B' > /dev/null");

## Revert content
write_file("repeat.txt", "Version A");
system("perl $cmd add repeat.txt > /dev/null");
system("perl $cmd commit -m 'Back to State A' > /dev/null");
my $tree_v3 = (bsd_glob(File::Spec->catfile(REV_DIR, read_file(HEAD), "tree-*")))[0];

is($tree_v1 =~ s/.*tree-//r, $tree_v3 =~ s/.*tree-//r, 
	"Tree hash reverted perfectly after content restoration");

# Cleanup
chdir($orig_dir);
# remove_tree($sandbox); # Uncomment this once you're sure it works

done_testing();

sub write_file {
	my ($path, $content) = @_;
	open my $fh, '>', $path or die "Could not open '$path' for writing: $!";
	print $fh $content;
	close $fh or die "Could not close '$path' after writing: $!";
}

sub read_file {
	my $path = shift;
	open my $fh, '<', $path or return "";
	my $content = <$fh>;
	chomp $content if $content;
	close $fh;
	return $content;
}