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 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
|
package TestFor::Code::TidyAll::Git;
use Capture::Tiny qw(capture capture_stderr);
use Code::TidyAll::Git::Util qw(git_files_to_commit git_modified_files);
use Code::TidyAll::Util qw(tempdir_simple);
use Code::TidyAll;
use File::pushd qw(pushd);
use File::Spec;
use FindBin qw( $Bin );
use IPC::System::Simple qw(capturex runx);
use Path::Tiny qw(path);
use Test::Class::Most parent => 'TestHelper::Test::Class';
use Try::Tiny;
use constant IS_WIN32 => $^O eq 'MSWin32';
my ( $precommit_hook_template, $prereceive_hook_template, $tidyall_ini_template );
$ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = 'G. Author';
$ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = 'git-author@example.com';
# Ignore local configuration files, which may change the default branch from
# "master" to "main".
$ENV{GIT_CONFIG_GLOBAL} = $ENV{GIT_CONFIG_SYSTEM} = File::Spec->devnull;
BEGIN {
if (IS_WIN32) {
__PACKAGE__->SKIP_CLASS(
q{These tests behave oddly on Windows (at least in Azure). I think it has to do with differences in how output is captured and possible also some line ending issues when the test plugins like UpperText are invoked.}
);
}
}
sub test_git : Tests {
my ($self) = @_;
return unless $self->require_executable('git');
my ( $temp_dir, $work_dir, $pushd ) = $self->_make_working_dir_and_repo;
subtest 'add foo.txt', sub {
$work_dir->child('foo.txt')->spew_raw("abc\n");
cmp_deeply( [ git_files_to_commit($work_dir) ], [], 'no files to commit' );
runx(qw( git add foo.txt ));
cmp_deeply(
[ map { $_->stringify } git_files_to_commit($work_dir) ],
[ $work_dir->child('foo.txt')->stringify ], 'one file to commit'
);
};
subtest 'attempt to commit untidy file', sub {
my $output = capture_stderr { system(qw( git commit -q -m changed -a )) };
like( $output, qr/1 file did not pass tidyall check/, '1 file did not pass tidyall check' );
like( $output, qr/needs tidying/, 'needs tidying' );
$self->_assert_something_to_commit($work_dir);
};
subtest 'successfully commit tidied file', sub {
$work_dir->child('foo.txt')->spew_raw("ABC\n");
my $output = capture_stderr { runx(qw( git commit -q -m changed -a )) };
like( $output, qr/\[checked\] foo\.txt/, 'checked foo.txt' );
$self->_assert_nothing_to_commit($work_dir);
};
subtest 'add another file which is tidied', sub {
$work_dir->child('bar.txt')->spew_raw('ABC');
runx(qw( git add bar.txt ));
runx(qw( git commit -q -m bar.txt ));
$work_dir->child('bar.txt')->spew('def');
cmp_deeply( [ git_files_to_commit($work_dir) ], [], 'no files to commit' );
cmp_deeply(
[ map { $_->stringify } git_modified_files($work_dir) ],
["$work_dir/bar.txt"],
'one file was modified'
);
};
my ( $shared_dir, $clone_dir );
subtest 'create bare repo and clone it', sub {
$shared_dir = $temp_dir->child('shared');
$clone_dir = $temp_dir->child('clone');
runx( qw( git clone -q --bare ), map { _quote_for_win32($_) } $work_dir, $shared_dir );
runx( qw( git clone -q ), map { _quote_for_win32($_) } $shared_dir, $clone_dir );
chdir($clone_dir);
$self->_assert_nothing_to_commit($work_dir);
};
my $prereceive_hook_file = $shared_dir->child(qw( hooks pre-receive ));
my $prereceive_hook = sprintf( $prereceive_hook_template, $self->_lib_dirs );
$prereceive_hook_file->spew($prereceive_hook);
$prereceive_hook_file->chmod(0775);
subtest 'untidy file and attempt to commit it via commit -a', sub {
$clone_dir->child('foo.txt')->spew_raw("def\n");
runx(qw( git commit -q -m changed -a ));
$self->_assert_nothing_to_commit($work_dir);
$self->_assert_branch_is_ahead_of_origin;
};
subtest 'cannot push untidy file', sub {
my $output = capture_stderr { system(qw( git push )) };
like( $output, qr/master -> master/, 'master -> master' );
like( $output, qr/1 file did not pass tidyall check/, '1 file did not pass tidyall check' );
like( $output, qr/needs tidying/, 'needs tidying' );
$self->_assert_branch_is_ahead_of_origin;
};
subtest 'can push tidied file', sub {
$clone_dir->child('foo.txt')->spew_raw("DEF\n");
capture_stderr { runx(qw( git commit -q -m changed -a )) };
$self->_assert_nothing_to_commit($work_dir);
my $output = capture_stderr { system(qw( git push )) };
like( $output, qr/master -> master/, 'push succeeded' );
$self->_assert_nothing_to_push;
};
subtest 'untidy file and commit it', sub {
$clone_dir->child('foo.txt')->spew_raw("def\n");
runx(qw( git commit -q -m changed -a ));
$self->_assert_nothing_to_commit($work_dir);
$self->_assert_branch_is_ahead_of_origin;
};
subtest 'cannot push when file is untidy', sub {
$self->_assert_branch_is_ahead_of_origin;
my $output = capture_stderr { system(qw( git push )) };
like( $output, qr/needs tidying/, 'needs tidying' );
$self->_assert_branch_is_ahead_of_origin;
};
subtest 'cannot push when file is untidy (2nd try)', sub {
$self->_assert_branch_is_ahead_of_origin;
my $output = capture_stderr { system(qw( git push )) };
like( $output, qr/needs tidying/, 'needs tidying' );
like( $output, qr/Identical push seen 2 times/, 'Identical push seen 2 times' );
$self->_assert_branch_is_ahead_of_origin;
};
}
sub test_copied_status : Tests {
my ($self) = @_;
return unless $self->require_executable('git');
my ( $temp_dir, $work_dir, $pushd ) = $self->_make_working_dir_and_repo;
my $foo_file = $work_dir->child('foo.txt');
# If the file isn't long enough the new file doesn't end up with the
# "copied" status.
$foo_file->spew_raw( "ABC\n" x 500 );
runx(qw( git add foo.txt ));
runx(qw( git commit -m foo ));
my $bar_file = $work_dir->child('bar.txt');
$bar_file->spew_raw( "ABC\n" x 500 );
$foo_file->spew_raw(q{});
runx(qw( git add foo.txt bar.txt ));
my $output = capture_stderr { runx(qw( git commit -m bar )) };
unlike(
$output,
qr/uninitialized value/i,
'no warnings from Code::TidyAll::Git::Util module'
);
}
sub test_precommit_stash_issues : Tests {
my ($self) = @_;
return unless $self->require_executable('git');
my ( $temp_dir, $work_dir, $pushd ) = $self->_make_working_dir_and_repo;
my $foo_file = $work_dir->child('foo.txt');
$foo_file->spew_raw("ABC\n");
my $bar_file = $work_dir->child('bar.txt');
$bar_file->spew_raw("DEF\n");
subtest 'commit two tidy files', sub {
$self->_assert_something_to_commit($work_dir);
runx(qw( git add foo.txt bar.txt ));
my $output = capture_stderr( sub { runx(qw( git commit -q -m two )) } );
like( $output, qr/\Q[checked] foo.txt/, 'tidyall checked foo.txt' );
like( $output, qr/\Q[checked] bar.txt/, 'tidyall checked bar.txt' );
$self->_assert_nothing_to_commit($work_dir);
};
$foo_file->spew_raw("abc\n");
$bar_file->spew_raw("abc\n");
subtest 'cannot commit untidy files', sub {
$self->_assert_something_to_commit($work_dir);
my $output = capture_stderr( sub { system(qw( git commit -q -a -m untidy )) } );
like(
$output,
qr/2 files did not pass tidyall check/,
'commit failed because 2 files are untidy'
);
$self->_assert_something_to_commit($work_dir);
};
$foo_file->spew_raw("ABC\n");
my $baz_file = $work_dir->child('baz.txt');
$baz_file->spew_raw("ABC\n");
subtest 'commit one valid file and working directory is left intact', sub {
runx(qw( git add foo.txt ));
my ( $stdout, $stderr ) = capture( sub { system(qw( git commit -q -m foo )) } );
like(
$stdout,
qr/modified:\s+bar\.txt/,
'commit shows bar.txt as still modified'
);
is_deeply( [ git_files_to_commit($work_dir) ], [], 'no files to commit' );
is_deeply(
[ git_modified_files($work_dir) ],
[$bar_file],
'bar.txt is still modified in working directory'
);
my $status = capturex(qw( git status --porcelain -unormal ));
like(
$status,
qr/^\?\?\s+baz.txt/m,
'baz.txt is still untracked in working directory'
);
};
$foo_file->spew_raw("abc\n");
subtest 'commit one invalid file and working directory is left intact', sub {
runx(qw( git add foo.txt ));
my ( undef, $stderr ) = capture( sub { system(qw( git commit -q -m foo )) } );
like(
$stderr,
qr/needs tidying/,
'commit fails because file is not tidied'
);
is_deeply(
[ git_files_to_commit($work_dir) ],
[$foo_file],
'foo.txt is still in the index'
);
is_deeply(
[ git_modified_files($work_dir) ],
[ $bar_file, $foo_file ],
'bar.txt is still modified in working directory'
);
my $status = capturex(qw( git status --porcelain -unormal ));
like(
$status,
qr/^\?\?\s+baz.txt/m,
'baz.txt is still untracked in working directory'
);
};
runx(qw( git clean -q -dxf ));
# We need to add to the stash so we can make sure that it's not popped
# incorrectly later.
$foo_file->spew_raw("abC\n");
runx(qw( git stash -q ));
subtest 'precommit hook does not pop when it did not stash', sub {
$foo_file->spew_raw("ABCD\n");
runx(qw( git commit -q -a -m changed ));
# The bug we're fixing is that this commit would always pop the stash,
# even though the Precommit hook's call to "git stash" hadn't _added_
# to the stash. This meant we'd end up potentially popping some random
# thing off the stash, making a huge mess.
my $e;
try { runx(qw( git commit -q --amend -m amended )) }
catch { $e = $_ };
is(
$e,
undef,
'no bogus pop amending a commit when the stash has old content'
);
};
}
# See https://github.com/houseabsolute/perl-code-tidyall/issues/100 for the
# bug we're testing.
sub test_precommit_no_stash_merge : Tests {
my ($self) = @_;
return unless $self->require_executable('git');
my ( $temp_dir, $work_dir, $pushd ) = $self->_make_working_dir_and_repo;
$work_dir->child('file1.txt')->spew("A\nB\n");
$work_dir->child('file2.txt')->spew("A\nB\n");
runx(qw( git add file1.txt file2.txt ));
runx( qw( git commit -m ), 'Add files in master' );
$work_dir->child('file1.txt')->append("C\n");
$work_dir->child('file2.txt')->append("C\n");
runx( qw( git commit -a -m ), 'Update files in master' );
runx(qw( git checkout -b my-branch ));
runx(qw( git reset --hard HEAD~1 ));
$work_dir->child('file1.txt')->append("D\n");
$work_dir->child('file2.txt')->append("C\n");
runx(qw( git add file1.txt file2.txt ));
runx( qw( git commit -m ), 'Update files in my-branch' );
# This will exit with 1 because of the conflict.
runx( [1], qw( git merge master ) );
like(
$work_dir->child(qw( .git MERGE_MSG ))->slurp,
qr/Conflicts:.+file1\.txt/s,
'merge produced a conflict with file.txt'
);
$work_dir->child('file1.txt')->spew("A\nB\nD\n");
# We need a change that will be stashed to trigger the bug.
$work_dir->child('file2.txt')->append("E\n");
runx(qw( git add file1.txt ));
runx( qw( git commit -m ), 'Add file1.txt in my-branch for real' );
my $output = capturex(qw( git log -n 1 ));
like( $output, qr/Merge: [0-9a-f]+ [0-9a-f]+/, 'last commit was a merge commit' );
}
sub _make_working_dir_and_repo {
my $self = shift;
my $temp_dir = tempdir_simple;
my $work_dir = $temp_dir->child('work');
my $hooks_dir = $work_dir->child(qw( .git hooks ));
runx( qw( git init -q ), _quote_for_win32($work_dir) );
# This dir doesn't exist unless there's a git dir template that includes
# the hooks subdir.
$hooks_dir->mkpath( 0, 0755 );
ok( -d $_, "$_ exists" ) for ( $work_dir, $hooks_dir );
my $pushd = pushd($work_dir);
$work_dir->child('tidyall.ini')->spew($tidyall_ini_template);
$work_dir->child('.gitignore')->spew('.tidyall.d');
runx(qw( git add tidyall.ini .gitignore ));
runx(qw( git commit -q -m added tidyall.ini .gitignore ));
my $precommit_hook_file = $hooks_dir->child('pre-commit');
my $precommit_hook = sprintf( $precommit_hook_template, $self->_lib_dirs );
$precommit_hook_file->spew($precommit_hook);
$precommit_hook_file->chmod(0755);
return ( $temp_dir, $work_dir, $pushd );
}
sub _quote_for_win32 {
# The docs for IPC::System::Simple lie about how it works on Windows. On
# Windows it _always_ invokes a shell, so we need to quote a path with
# spaces.
return $_[0] unless IS_WIN32 && $_[0] =~ / /;
return qq{"$_[0]"};
}
sub _lib_dirs {
my %dirs = map { $_ => 1 } map { path($Bin)->parent->child($_) } qw( lib t/lib );
if ( $ENV{PERL5LIB} ) {
my $sep = $^O eq 'MSWin32' ? q{;} : q{:};
$dirs{$_} = 1 for split /\Q$sep/, $ENV{PERL5LIB};
}
return join q{ }, sort keys %dirs;
}
sub _assert_nothing_to_commit {
shift;
my @files = git_files_to_commit(shift);
is( scalar @files, 0, 'there are no files to commit' )
or diag("@files");
}
sub _assert_something_to_commit {
shift;
my @files = git_files_to_commit(shift);
cmp_ok( scalar @files, '>=', 0, 'there are files to commit' );
}
sub _assert_nothing_to_push {
unlike(
capturex( 'git', 'status' ),
qr/Your branch is ahead/,
'branch is up to date with origin'
);
}
sub _assert_branch_is_ahead_of_origin {
like( capturex( 'git', 'status' ), qr/Your branch is ahead/, 'branch is ahead of origin' );
}
$precommit_hook_template = '#!' . $^X . "\n" . <<'EOF';
use lib qw(%s);
use Code::TidyAll::Git::Precommit;
use strict;
use warnings;
Code::TidyAll::Git::Precommit->check(
tidyall_options => { verbose => 1 }
);
EOF
$prereceive_hook_template = '#!' . $^X . "\n" . <<'EOF';
use lib qw(%s);
use Code::TidyAll::Git::Prereceive;
use strict;
use warnings;
Code::TidyAll::Git::Prereceive->check();
EOF
if (IS_WIN32) {
for my $t ( $precommit_hook_template, $prereceive_hook_template ) {
( my $perl = $^X ) =~ s{\\}{/}g;
$t = qq{#!/bin/sh\n$perl -e '$t'};
}
}
$tidyall_ini_template = <<'EOF';
[+TestHelper::Plugin::UpperText]
select = **/*.txt
EOF
1;
|