1#!/usr/bin/perl -w 2 3use strict; 4use warnings; 5use utf8; 6use Test::More tests => 248; 7#use Test::More 'no_plan'; 8use App::Sqitch; 9use Locale::TextDomain qw(App-Sqitch); 10use Test::NoWarnings; 11use Test::Exception; 12use Test::MockModule; 13use Path::Class; 14use Term::ANSIColor qw(color); 15use Encode; 16use lib 't/lib'; 17use MockOutput; 18use LC; 19 20$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; 21$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; 22$ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; 23 24my $CLASS = 'App::Sqitch::Command::log'; 25require_ok $CLASS; 26 27ok my $sqitch = App::Sqitch->new( 28 options => { 29 engine => 'sqlite', 30 top_dir => Path::Class::Dir->new('test-log')->stringify, 31 plan_file => Path::Class::File->new('t/sql/sqitch.plan')->stringify, 32 }, 33), 'Load a sqitch sqitch object'; 34my $config = $sqitch->config; 35isa_ok my $log = App::Sqitch::Command->load({ 36 sqitch => $sqitch, 37 command => 'log', 38 config => $config, 39}), $CLASS, 'log command'; 40 41can_ok $log, qw( 42 target 43 change_pattern 44 project_pattern 45 committer_pattern 46 max_count 47 skip 48 reverse 49 format 50 options 51 execute 52 configure 53); 54 55is_deeply [$CLASS->options], [qw( 56 event=s@ 57 target|t=s 58 change-pattern|change=s 59 project-pattern|project=s 60 committer-pattern|committer=s 61 format|f=s 62 date-format|date=s 63 max-count|n=i 64 skip=i 65 reverse! 66 color=s 67 no-color 68 abbrev=i 69 oneline 70)], 'Options should be correct'; 71 72############################################################################## 73# Test database. 74is $log->target, undef, 'Default target should be undef'; 75isa_ok $log = $CLASS->new( 76 sqitch => $sqitch, 77 target => 'foo', 78), $CLASS, 'new status with target'; 79is $log->target, 'foo', 'Should have target "foo"'; 80 81############################################################################## 82# Test configure(). 83my $cmock = Test::MockModule->new('App::Sqitch::Config'); 84 85# Test date_format validation. 86my $configured = $CLASS->configure($config, {}); 87isa_ok delete $configured->{formatter}, 'App::Sqitch::ItemFormatter', 'Formatter'; 88is_deeply $configured, {}, 89 'Should get empty hash for no config or options'; 90$cmock->mock( get => 'nonesuch' ); 91throws_ok { $CLASS->configure($config, {}), {} } 'App::Sqitch::X', 92 'Should get error for invalid date format in config'; 93is $@->ident, 'datetime', 94 'Invalid date format error ident should be "datetime"'; 95is $@->message, __x( 96 'Unknown date format "{format}"', 97 format => 'nonesuch', 98), 'Invalid date format error message should be correct'; 99$cmock->unmock_all; 100 101throws_ok { $CLASS->configure($config, { date_format => 'non'}), {} } 102 'App::Sqitch::X', 103 'Should get error for invalid date format in optsions'; 104is $@->ident, 'datetime', 105 'Invalid date format error ident should be "log"'; 106is $@->message, __x( 107 'Unknown date format "{format}"', 108 format => 'non', 109), 'Invalid date format error message should be correct'; 110 111# Test format validation. 112$cmock->mock( get => sub { 113 my ($self, %p) = @_; 114 return 'nonesuch' if $p{key} eq 'log.format'; 115 return undef; 116}); 117throws_ok { $CLASS->configure($config, {}), {} } 'App::Sqitch::X', 118 'Should get error for invalid format in config'; 119is $@->ident, 'log', 120 'Invalid format error ident should be "log"'; 121is $@->message, __x( 122 'Unknown log format "{format}"', 123 format => 'nonesuch', 124), 'Invalid format error message should be correct'; 125$cmock->unmock_all; 126 127throws_ok { $CLASS->configure($config, { format => 'non'}), {} } 128 'App::Sqitch::X', 129 'Should get error for invalid format in optsions'; 130is $@->ident, 'log', 131 'Invalid format error ident should be "log"'; 132is $@->message, __x( 133 'Unknown log format "{format}"', 134 format => 'non', 135), 'Invalid format error message should be correct'; 136 137# Test color configuration. 138$configured = $CLASS->configure( $config, { no_color => 1 } ); 139is $configured->{formatter}->color, 'never', 140 'Configuration should respect --no-color, setting "never"'; 141 142# Test oneline configuration. 143$configured = $CLASS->configure( $config, { oneline => 1 }); 144is $configured->{format}, '%{:event}C%h %l%{reset}C %o:%n %s', 145 '--oneline should set format'; 146is $configured->{formatter}{abbrev}, 6, '--oneline should set abbrev to 6'; 147 148$configured = $CLASS->configure( $config, { oneline => 1, format => 'format:foo', abbrev => 5 }); 149is $configured->{format}, 'foo', '--oneline should not override --format'; 150is $configured->{formatter}{abbrev}, 5, '--oneline should not overrride --abbrev'; 151 152my $config_color = 'auto'; 153$cmock->mock( get => sub { 154 my ($self, %p) = @_; 155 return $config_color if $p{key} eq 'log.color'; 156 return undef; 157}); 158 159my $log_config = {}; 160$cmock->mock( get_section => sub { $log_config } ); 161 162$configured = $CLASS->configure( $config, { no_color => 1 } ); 163 164is $configured->{formatter}->color, 'never', 165 'Configuration should respect --no-color even when configure is set'; 166 167NEVER: { 168 $config_color = 'never'; 169 $log_config = { color => $config_color }; 170 my $configured = $CLASS->configure( $config, $log_config ); 171 is $configured->{formatter}->color, 'never', 172 'Configuration should respect color option'; 173 174 # Try it with config. 175 $log_config = { color => $config_color }; 176 $configured = $CLASS->configure( $config, {} ); 177 is $configured->{formatter}->color, 'never', 178 'Configuration should respect color config'; 179} 180 181ALWAYS: { 182 $config_color = 'always'; 183 $log_config = { color => $config_color }; 184 my $configured = $CLASS->configure( $config, $log_config ); 185 is_deeply $configured->{formatter}->color, 'always', 186 'Configuration should respect color option'; 187 188 # Try it with config. 189 $log_config = { color => $config_color }; 190 $configured = $CLASS->configure( $config, {} ); 191 is_deeply $configured->{formatter}->color, 'always', 192 'Configuration should respect color config'; 193} 194 195AUTO: { 196 $config_color = 'auto'; 197 $log_config = { color => $config_color }; 198 for my $enabled (0, 1) { 199 my $configured = $CLASS->configure( $config, $log_config ); 200 is_deeply $configured->{formatter}->color, 'auto', 201 'Configuration should respect color option'; 202 203 # Try it with config. 204 $log_config = { color => $config_color }; 205 $configured = $CLASS->configure( $config, {} ); 206 is_deeply $configured->{formatter}->color, 'auto', 207 'Configuration should respect color config'; 208 } 209} 210 211$cmock->unmock_all; 212 213############################################################################### 214# Test named formats. 215my $cdt = App::Sqitch::DateTime->now; 216my $pdt = $cdt->clone->subtract(days => 1); 217my $event = { 218 event => 'deploy', 219 project => 'logit', 220 change_id => '000011112222333444', 221 change => 'lolz', 222 tags => [ '@beta', '@gamma' ], 223 committer_name => 'larry', 224 committer_email => 'larry@example.com', 225 committed_at => $cdt, 226 planner_name => 'damian', 227 planner_email => 'damian@example.com', 228 planned_at => $pdt, 229 note => "For the LOLZ.\n\nYou know, funny stuff and cute kittens, right?", 230 requires => [qw(foo bar)], 231 conflicts => [] 232}; 233 234my $ciso = $cdt->as_string( format => 'iso' ); 235my $craw = $cdt->as_string( format => 'raw' ); 236my $piso = $pdt->as_string( format => 'iso' ); 237my $praw = $pdt->as_string( format => 'raw' ); 238for my $spec ( 239 [ raw => "deploy 000011112222333444 (\@beta, \@gamma)\n" 240 . "name lolz\n" 241 . "project logit\n" 242 . "requires foo, bar\n" 243 . "planner damian <damian\@example.com>\n" 244 . "planned $praw\n" 245 . "committer larry <larry\@example.com>\n" 246 . "committed $craw\n\n" 247 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 248 ], 249 [ full => __('Deploy') . " 000011112222333444 (\@beta, \@gamma)\n" 250 . __('Name: ') . " lolz\n" 251 . __('Project: ') . " logit\n" 252 . __('Requires: ') . " foo, bar\n" 253 . __('Planner: ') . " damian <damian\@example.com>\n" 254 . __('Planned: ') . " __PDATE__\n" 255 . __('Committer:') . " larry <larry\@example.com>\n" 256 . __('Committed:') . " __CDATE__\n\n" 257 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 258 ], 259 [ long => __('Deploy') . " 000011112222333444 (\@beta, \@gamma)\n" 260 . __('Name: ') . " lolz\n" 261 . __('Project: ') . " logit\n" 262 . __('Planner: ') . " damian <damian\@example.com>\n" 263 . __('Committer:') . " larry <larry\@example.com>\n\n" 264 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 265 ], 266 [ medium => __('Deploy') . " 000011112222333444\n" 267 . __('Name: ') . " lolz\n" 268 . __('Committer:') . " larry <larry\@example.com>\n" 269 . __('Date: ') . " __CDATE__\n\n" 270 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 271 ], 272 [ short => __('Deploy') . " 000011112222333444\n" 273 . __('Name: ') . " lolz\n" 274 . __('Committer:') . " larry <larry\@example.com>\n\n" 275 . " For the LOLZ.\n", 276 ], 277 [ oneline => '000011112222333444 ' . __('deploy') . ' logit:lolz For the LOLZ.' ], 278) { 279 local $ENV{ANSI_COLORS_DISABLED} = 1; 280 my $configured = $CLASS->configure( $config, { format => $spec->[0] } ); 281 my $format = $configured->{format}; 282 ok my $log = $CLASS->new( sqitch => $sqitch, %{ $configured } ), 283 qq{Instantiate with format "$spec->[0]"}; 284 (my $exp = $spec->[1]) =~ s/__CDATE__/$ciso/; 285 $exp =~ s/__PDATE__/$piso/; 286 is $log->formatter->format( $log->format, $event ), $exp, 287 qq{Format "$spec->[0]" should output correctly}; 288 289 if ($spec->[1] =~ /__CDATE__/) { 290 # Test different date formats. 291 for my $date_format (qw(rfc long medium)) { 292 ok my $log = $CLASS->new( 293 sqitch => $sqitch, 294 format => $format, 295 formatter => App::Sqitch::ItemFormatter->new(date_format => $date_format), 296 ), qq{Instantiate with format "$spec->[0]" and date format "$date_format"}; 297 my $date = $cdt->as_string( format => $date_format ); 298 (my $exp = $spec->[1]) =~ s/__CDATE__/$date/; 299 $date = $pdt->as_string( format => $date_format ); 300 $exp =~ s/__PDATE__/$date/; 301 is $log->formatter->format( $log->format, $event ), $exp, 302 qq{Format "$spec->[0]" and date format "$date_format" should output correctly}; 303 } 304 } 305 306 if ($spec->[1] =~ s/\s+[(]?[@]beta,\s+[@]gamma[)]?//) { 307 # Test without tags. 308 local $event->{tags} = []; 309 (my $exp = $spec->[1]) =~ s/__CDATE__/$ciso/; 310 $exp =~ s/__PDATE__/$piso/; 311 is $log->formatter->format( $log->format, $event ), $exp, 312 qq{Format "$spec->[0]" should output correctly without tags}; 313 } 314} 315 316############################################################################### 317# Test all formatting characters. 318my $local_cdt = $cdt->clone; 319$local_cdt->set_time_zone('local'); 320$local_cdt->set( locale => $LC::TIME ); 321my $local_pdt = $pdt->clone; 322$local_pdt->set_time_zone('local'); 323$local_pdt->set( locale => $LC::TIME ); 324 325my $formatter = $log->formatter; 326for my $spec ( 327 ['%e', { event => 'deploy' }, 'deploy' ], 328 ['%e', { event => 'revert' }, 'revert' ], 329 ['%e', { event => 'fail' }, 'fail' ], 330 331 ['%L', { event => 'deploy' }, __ 'Deploy' ], 332 ['%L', { event => 'revert' }, __ 'Revert' ], 333 ['%L', { event => 'fail' }, __ 'Fail' ], 334 335 ['%l', { event => 'deploy' }, __ 'deploy' ], 336 ['%l', { event => 'revert' }, __ 'revert' ], 337 ['%l', { event => 'fail' }, __ 'fail' ], 338 339 ['%{event}_', {}, __ 'Event: ' ], 340 ['%{change}_', {}, __ 'Change: ' ], 341 ['%{committer}_', {}, __ 'Committer:' ], 342 ['%{planner}_', {}, __ 'Planner: ' ], 343 ['%{by}_', {}, __ 'By: ' ], 344 ['%{date}_', {}, __ 'Date: ' ], 345 ['%{committed}_', {}, __ 'Committed:' ], 346 ['%{planned}_', {}, __ 'Planned: ' ], 347 ['%{name}_', {}, __ 'Name: ' ], 348 ['%{email}_', {}, __ 'Email: ' ], 349 ['%{requires}_', {}, __ 'Requires: ' ], 350 ['%{conflicts}_', {}, __ 'Conflicts:' ], 351 352 ['%H', { change_id => '123456789' }, '123456789' ], 353 ['%h', { change_id => '123456789' }, '123456789' ], 354 ['%{5}h', { change_id => '123456789' }, '12345' ], 355 ['%{7}h', { change_id => '123456789' }, '1234567' ], 356 357 ['%n', { change => 'foo' }, 'foo'], 358 ['%n', { change => 'bar' }, 'bar'], 359 ['%o', { project => 'foo' }, 'foo'], 360 ['%o', { project => 'bar' }, 'bar'], 361 362 ['%c', { committer_name => 'larry', committer_email => 'larry@example.com' }, 'larry <larry@example.com>'], 363 ['%{n}c', { committer_name => 'damian' }, 'damian'], 364 ['%{name}c', { committer_name => 'chip' }, 'chip'], 365 ['%{e}c', { committer_email => 'larry@example.com' }, 'larry@example.com'], 366 ['%{email}c', { committer_email => 'damian@example.com' }, 'damian@example.com'], 367 368 ['%{date}c', { committed_at => $cdt }, $cdt->as_string( format => 'iso' ) ], 369 ['%{date:rfc}c', { committed_at => $cdt }, $cdt->as_string( format => 'rfc' ) ], 370 ['%{d:long}c', { committed_at => $cdt }, $cdt->as_string( format => 'long' ) ], 371 ["%{d:cldr:HH'h' mm'm'}c", { committed_at => $cdt }, $local_cdt->format_cldr( q{HH'h' mm'm'} ) ], 372 ["%{d:strftime:%a at %H:%M:%S}c", { committed_at => $cdt }, $local_cdt->strftime('%a at %H:%M:%S') ], 373 374 ['%p', { planner_name => 'larry', planner_email => 'larry@example.com' }, 'larry <larry@example.com>'], 375 ['%{n}p', { planner_name => 'damian' }, 'damian'], 376 ['%{name}p', { planner_name => 'chip' }, 'chip'], 377 ['%{e}p', { planner_email => 'larry@example.com' }, 'larry@example.com'], 378 ['%{email}p', { planner_email => 'damian@example.com' }, 'damian@example.com'], 379 380 ['%{date}p', { planned_at => $pdt }, $pdt->as_string( format => 'iso' ) ], 381 ['%{date:rfc}p', { planned_at => $pdt }, $pdt->as_string( format => 'rfc' ) ], 382 ['%{d:long}p', { planned_at => $pdt }, $pdt->as_string( format => 'long' ) ], 383 ["%{d:cldr:HH'h' mm'm'}p", { planned_at => $pdt }, $local_pdt->format_cldr( q{HH'h' mm'm'} ) ], 384 ["%{d:strftime:%a at %H:%M:%S}p", { planned_at => $pdt }, $local_pdt->strftime('%a at %H:%M:%S') ], 385 386 ['%t', { tags => [] }, '' ], 387 ['%t', { tags => ['@foo'] }, ' @foo' ], 388 ['%t', { tags => ['@foo', '@bar'] }, ' @foo, @bar' ], 389 ['%{|}t', { tags => [] }, '' ], 390 ['%{|}t', { tags => ['@foo'] }, ' @foo' ], 391 ['%{|}t', { tags => ['@foo', '@bar'] }, ' @foo|@bar' ], 392 393 ['%T', { tags => [] }, '' ], 394 ['%T', { tags => ['@foo'] }, ' (@foo)' ], 395 ['%T', { tags => ['@foo', '@bar'] }, ' (@foo, @bar)' ], 396 ['%{|}T', { tags => [] }, '' ], 397 ['%{|}T', { tags => ['@foo'] }, ' (@foo)' ], 398 ['%{|}T', { tags => ['@foo', '@bar'] }, ' (@foo|@bar)' ], 399 400 ['%r', { requires => [] }, '' ], 401 ['%r', { requires => ['foo'] }, ' foo' ], 402 ['%r', { requires => ['foo', 'bar'] }, ' foo, bar' ], 403 ['%{|}r', { requires => [] }, '' ], 404 ['%{|}r', { requires => ['foo'] }, ' foo' ], 405 ['%{|}r', { requires => ['foo', 'bar'] }, ' foo|bar' ], 406 407 ['%R', { requires => [] }, '' ], 408 ['%R', { requires => ['foo'] }, __('Requires: ') . " foo\n" ], 409 ['%R', { requires => ['foo', 'bar'] }, __('Requires: ') . " foo, bar\n" ], 410 ['%{|}R', { requires => [] }, '' ], 411 ['%{|}R', { requires => ['foo'] }, __('Requires: ') . " foo\n" ], 412 ['%{|}R', { requires => ['foo', 'bar'] }, __('Requires: ') . " foo|bar\n" ], 413 414 ['%x', { conflicts => [] }, '' ], 415 ['%x', { conflicts => ['foo'] }, ' foo' ], 416 ['%x', { conflicts => ['foo', 'bax'] }, ' foo, bax' ], 417 ['%{|}x', { conflicts => [] }, '' ], 418 ['%{|}x', { conflicts => ['foo'] }, ' foo' ], 419 ['%{|}x', { conflicts => ['foo', 'bax'] }, ' foo|bax' ], 420 421 ['%X', { conflicts => [] }, '' ], 422 ['%X', { conflicts => ['foo'] }, __('Conflicts:') . " foo\n" ], 423 ['%X', { conflicts => ['foo', 'bar'] }, __('Conflicts:') . " foo, bar\n" ], 424 ['%{|}X', { conflicts => [] }, '' ], 425 ['%{|}X', { conflicts => ['foo'] }, __('Conflicts:') . " foo\n" ], 426 ['%{|}X', { conflicts => ['foo', 'bar'] }, __('Conflicts:') . " foo|bar\n" ], 427 428 ['%{yellow}C', {}, '' ], 429 ['%{:event}C', { event => 'deploy' }, '' ], 430 ['%v', {}, "\n" ], 431 ['%%', {}, '%' ], 432 433 ['%s', { note => 'hi there' }, 'hi there' ], 434 ['%s', { note => "hi there\nyo" }, 'hi there' ], 435 ['%s', { note => "subject line\n\nfirst graph\n\nsecond graph\n\n" }, 'subject line' ], 436 ['%{ }s', { note => 'hi there' }, ' hi there' ], 437 ['%{xx}s', { note => 'hi there' }, 'xxhi there' ], 438 439 ['%b', { note => 'hi there' }, '' ], 440 ['%b', { note => "hi there\nyo" }, 'yo' ], 441 ['%b', { note => "subject line\n\nfirst graph\n\nsecond graph\n\n" }, "first graph\n\nsecond graph\n\n" ], 442 ['%{ }b', { note => 'hi there' }, '' ], 443 ['%{xxx }b', { note => "hi there\nyo" }, "xxx yo" ], 444 ['%{x}b', { note => "subject line\n\nfirst graph\n\nsecond graph\n\n" }, "xfirst graph\nx\nxsecond graph\nx\n" ], 445 ['%{ }b', { note => "hi there\r\nyo" }, " yo" ], 446 447 ['%B', { note => 'hi there' }, 'hi there' ], 448 ['%B', { note => "hi there\nyo" }, "hi there\nyo" ], 449 ['%B', { note => "subject line\n\nfirst graph\n\nsecond graph\n\n" }, "subject line\n\nfirst graph\n\nsecond graph\n\n" ], 450 ['%{ }B', { note => 'hi there' }, ' hi there' ], 451 ['%{xxx }B', { note => "hi there\nyo" }, "xxx hi there\nxxx yo" ], 452 ['%{x}B', { note => "subject line\n\nfirst graph\n\nsecond graph\n\n" }, "xsubject line\nx\nxfirst graph\nx\nxsecond graph\nx\n" ], 453 ['%{ }B', { note => "hi there\r\nyo" }, " hi there\r\n yo" ], 454 455 ['%{change}a', $event, "change $event->{change}\n" ], 456 ['%{change_id}a', $event, "change_id $event->{change_id}\n" ], 457 ['%{event}a', $event, "event $event->{event}\n" ], 458 ['%{tags}a', $event, 'tags ' . join(', ', @{ $event->{tags} }) . "\n" ], 459 ['%{requires}a', $event, 'requires ' . join(', ', @{ $event->{requires} }) . "\n" ], 460 ['%{conflicts}a', $event, '' ], 461 ['%{committer_name}a', $event, "committer_name $event->{committer_name}\n" ], 462 ['%{committed_at}a', $event, "committed_at $craw\n" ], 463) { 464 local $ENV{ANSI_COLORS_DISABLED} = 1; 465 (my $desc = encode_utf8 $spec->[2]) =~ s/\n/[newline]/g; 466 is $formatter->format( $spec->[0], $spec->[1] ), $spec->[2], 467 qq{Format "$spec->[0]" should output "$desc"}; 468} 469 470throws_ok { $formatter->format( '%_', {} ) } 'App::Sqitch::X', 471 'Should get exception for format "%_"'; 472is $@->ident, 'format', '%_ error ident should be "format"'; 473is $@->message, __ 'No label passed to the _ format', 474 '%_ error message should be correct'; 475throws_ok { $formatter->format( '%{foo}_', {} ) } 'App::Sqitch::X', 476 'Should get exception for unknown label in format "%_"'; 477is $@->ident, 'format', 'Invalid %_ label error ident should be "format"'; 478is $@->message, __x( 479 'Unknown label "{label}" passed to the _ format', 480 label => 'foo' 481), 'Invalid %_ label error message should be correct'; 482 483ok $log = $CLASS->new( 484 sqitch => $sqitch, 485 formatter => App::Sqitch::ItemFormatter->new(abbrev => 4) 486), 'Instantiate with abbrev => 4'; 487is $log->formatter->format( '%h', { change_id => '123456789' } ), 488 '1234', '%h should respect abbrev'; 489is $log->formatter->format( '%H', { change_id => '123456789' } ), 490 '123456789', '%H should not respect abbrev'; 491 492ok $log = $CLASS->new( 493 sqitch => $sqitch, 494 formatter => App::Sqitch::ItemFormatter->new(date_format => 'rfc') 495), 'Instantiate with date_format => "rfc"'; 496is $log->formatter->format( '%{date}c', { committed_at => $cdt } ), 497 $cdt->as_string( format => 'rfc' ), 498 '%{date}c should respect the date_format attribute'; 499is $log->formatter->format( '%{d:iso}c', { committed_at => $cdt } ), 500 $cdt->as_string( format => 'iso' ), 501 '%{iso}c should override the date_format attribute'; 502 503throws_ok { $formatter->format( '%{foo}a', {}) } 'App::Sqitch::X', 504 'Should get exception for unknown attribute passed to %a'; 505is $@->ident, 'format', '%a error ident should be "format"'; 506is $@->message, __x( 507 '{attr} is not a valid change attribute', attr => 'foo' 508), '%a error message should be correct'; 509 510 511delete $ENV{ANSI_COLORS_DISABLED}; 512for my $color (qw(yellow red blue cyan magenta)) { 513 is $formatter->format( "%{$color}C", {} ), color($color), 514 qq{Format "%{$color}C" should output } 515 . color($color) . $color . color('reset'); 516} 517 518for my $spec ( 519 [ ':event', { event => 'deploy' }, 'green', 'deploy' ], 520 [ ':event', { event => 'revert' }, 'blue', 'revert' ], 521 [ ':event', { event => 'fail' }, 'red', 'fail' ], 522) { 523 is $formatter->format( "%{$spec->[0]}C", $spec->[1] ), color($spec->[2]), 524 qq{Format "%{$spec->[0]}C" on "$spec->[3]" should output } 525 . color($spec->[2]) . $spec->[2] . color('reset'); 526} 527 528# Make sure other colors work. 529my $yellow = color('yellow') . '%s' . color('reset'); 530my $green = color('green') . '%s' . color('reset'); 531$event->{conflicts} = [qw(dr_evil)]; 532for my $spec ( 533 [ full => sprintf($green, __ ('Deploy') . ' 000011112222333444') 534 . " (\@beta, \@gamma)\n" 535 . __ ('Name: ') . " lolz\n" 536 . __ ('Project: ') . " logit\n" 537 . __ ('Requires: ') . " foo, bar\n" 538 . __ ('Conflicts:') . " dr_evil\n" 539 . __ ('Planner: ') . " damian <damian\@example.com>\n" 540 . __ ('Planned: ') . " __PDATE__\n" 541 . __ ('Committer:') . " larry <larry\@example.com>\n" 542 . __ ('Committed:') . " __CDATE__\n\n" 543 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 544 ], 545 [ long => sprintf($green, __ ('Deploy') . ' 000011112222333444') 546 . " (\@beta, \@gamma)\n" 547 . __ ('Name: ') . " lolz\n" 548 . __ ('Project: ') . " logit\n" 549 . __ ('Planner: ') . " damian <damian\@example.com>\n" 550 . __ ('Committer:') . " larry <larry\@example.com>\n\n" 551 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 552 ], 553 [ medium => sprintf($green, __ ('Deploy') . ' 000011112222333444') . "\n" 554 . __ ('Name: ') . " lolz\n" 555 . __ ('Committer:') . " larry <larry\@example.com>\n" 556 . __ ('Date: ') . " __CDATE__\n\n" 557 . " For the LOLZ.\n \n You know, funny stuff and cute kittens, right?\n" 558 ], 559 [ short => sprintf($green, __ ('Deploy') . ' 000011112222333444') . "\n" 560 . __ ('Name: ') . " lolz\n" 561 . __ ('Committer:') . " larry <larry\@example.com>\n\n" 562 . " For the LOLZ.\n", 563 ], 564 [ oneline => sprintf "$green %s %s", '000011112222333444' . ' ' 565 . __('deploy'), 'logit:lolz', 'For the LOLZ.', 566 ], 567) { 568 my $format = $CLASS->configure( $config, { format => $spec->[0] } )->{format}; 569 ok my $log = $CLASS->new( sqitch => $sqitch, format => $format ), 570 qq{Instantiate with format "$spec->[0]" again}; 571 (my $exp = $spec->[1]) =~ s/__CDATE__/$ciso/; 572 $exp =~ s/__PDATE__/$piso/; 573 is $log->formatter->format( $log->format, $event ), $exp, 574 qq{Format "$spec->[0]" should output correctly with color}; 575} 576 577throws_ok { $formatter->format( '%{BLUELOLZ}C', {} ) } 'App::Sqitch::X', 578 'Should get an error for an invalid color'; 579is $@->ident, 'format', 'Invalid color error ident should be "format"'; 580is $@->message, __x( 581 '{color} is not a valid ANSI color', color => 'BLUELOLZ' 582), 'Invalid color error message should be correct'; 583 584############################################################################## 585# Test execute(). 586my $emock = Test::MockModule->new('App::Sqitch::Engine::sqlite'); 587$emock->mock(destination => 'flipr'); 588 589my $mock_target = Test::MockModule->new('App::Sqitch::Target'); 590my ($target_name_arg, $orig_meth); 591$target_name_arg = '_blah'; 592$mock_target->mock(new => sub { 593 my $self = shift; 594 my %p = @_; 595 $target_name_arg = $p{name}; 596 $self->$orig_meth(@_); 597}); 598$orig_meth = $mock_target->original('new'); 599 600# First test for uninitialized DB. 601my $init = 0; 602$emock->mock(initialized => sub { $init }); 603throws_ok { $log->execute } 'App::Sqitch::X', 604 'Should get exception for unititialied db'; 605is $@->ident, 'log', 'Uninit db error ident should be "log"'; 606is $@->exitval, 1, 'Uninit db exit val should be 1'; 607is $@->message, __x( 608 'Database {db} has not been initialized for Sqitch', 609 db => 'db:sqlite:', 610), 'Uninit db error message should be correct'; 611is $target_name_arg, undef, 'Should have passed undef to Target'; 612 613# Next, test for no events. 614$init = 1; 615$target_name_arg = '_blah'; 616my @events; 617my $iter = sub { shift @events }; 618my $search_args; 619$emock->mock(search_events => sub { 620 shift; 621 $search_args = [@_]; 622 return $iter; 623}); 624$log = $CLASS->new(sqitch => $sqitch); 625throws_ok { $log->execute } 'App::Sqitch::X', 626 'Should get error for empty event table'; 627is $@->ident, 'log', 'no events error ident should be "log"'; 628is $@->exitval, 1, 'no events exit val should be 1'; 629is $@->message, __x( 630 'No events logged for {db}', 631 db => 'flipr', 632), 'no events error message should be correct'; 633is_deeply $search_args, [limit => 1], 634 'Search should have been limited to one row'; 635is $target_name_arg, undef, 'Should have passed undef to Target again'; 636 637# Okay, let's add some events. 638push @events => {}, $event; 639$target_name_arg = '_blah'; 640$log = $CLASS->new(sqitch => $sqitch); 641ok $log->execute, 'Execute log'; 642is $target_name_arg, undef, 'Should have passed undef to Target once more'; 643is_deeply $search_args, [ 644 event => undef, 645 change => undef, 646 project => undef, 647 committer => undef, 648 limit => undef, 649 offset => undef, 650 direction => 'DESC' 651], 'The proper args should have been passed to search_events'; 652 653is_deeply +MockOutput->get_page, [ 654 [__x 'On database {db}', db => 'flipr'], 655 [ $log->formatter->format( $log->format, $event ) ], 656], 'The change should have been paged'; 657 658# Make sure a passed target is processed. 659push @events => {}, $event; 660$target_name_arg = '_blah'; 661ok $log->execute('db:sqlite:whatever.db'), 'Execute with target arg'; 662is $target_name_arg, 'db:sqlite:whatever.db', 663 'Target name should have been passed to Target'; 664is_deeply $search_args, [ 665 event => undef, 666 change => undef, 667 project => undef, 668 committer => undef, 669 limit => undef, 670 offset => undef, 671 direction => 'DESC' 672], 'The proper args should have been passed to search_events'; 673 674is_deeply +MockOutput->get_page, [ 675 [__x 'On database {db}', db => 'flipr'], 676 [ $log->formatter->format( $log->format, $event ) ], 677], 'The change should have been paged'; 678 679# Set attributes and add more events. 680my $event2 = { 681 event => 'revert', 682 change_id => '84584584359345', 683 change => 'barf', 684 tags => [], 685 committer_name => 'theory', 686 committer_email => 'theory@example.com', 687 committed_at => $cdt, 688 note => 'Oh man this was a bad idea', 689}; 690push @events => {}, $event, $event2; 691isa_ok $log = $CLASS->new( 692 sqitch => $sqitch, 693 target => 'db:sqlite:foo.db', 694 event => [qw(revert fail)], 695 change_pattern => '.+', 696 project_pattern => '.+', 697 committer_pattern => '.+', 698 max_count => 10, 699 skip => 5, 700 reverse => 1, 701), $CLASS, 'log with attributes'; 702 703$target_name_arg = '_blah'; 704ok $log->execute, 'Execute log with attributes'; 705is $target_name_arg, $log->target, 'Should have passed target name to Target'; 706is_deeply $search_args, [ 707 event => [qw(revert fail)], 708 change => '.+', 709 project => '.+', 710 committer => '.+', 711 limit => 10, 712 offset => 5, 713 direction => 'ASC' 714], 'All params should have been passed to search_events'; 715 716is_deeply +MockOutput->get_page, [ 717 [__x 'On database {db}', db => 'flipr'], 718 [ $log->formatter->format( $log->format, $event ) ], 719 [ $log->formatter->format( $log->format, $event2 ) ], 720], 'Both changes should have been paged'; 721 722# Make sure we get a warning when both the option and the arg are specified. 723push @events => {}, $event; 724ok $log->execute('foo'), 'Execute log with attributes'; 725is $target_name_arg, $log->target, 'Should have passed target name to Target'; 726is_deeply +MockOutput->get_warn, [[__x( 727 'Both the --target option and the target argument passed; using {option}', 728 option => $log->target, 729)]], 'Should have got warning for two targets'; 730 731# Make sure we catch bad format codes. 732isa_ok $log = $CLASS->new( 733 sqitch => $sqitch, 734 format => '%Z', 735), $CLASS, 'log with bad format'; 736 737push @events, {}, $event; 738$target_name_arg = '_blah'; 739throws_ok { $log->execute } 'App::Sqitch::X', 740 'Should get an exception for a bad format code'; 741is $@->ident, 'format', 742 'bad format code format error ident should be "format"'; 743is $@->message, __x( 744 'Unknown format code "{code}"', code => 'Z', 745), 'bad format code format error message should be correct'; 746is $target_name_arg, $log->target, 'Should have passed target name to Target'; 747