Trog-DataSource-ProvisionedVirt-spawn.t 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. #!/usr/bin/env perl
  2. # spawn() for real: it forks, detaches, and runs a provisioner, which here is
  3. # a shell script in a sandbox. t/Trog-DataSource-ProvisionedVirt.t mocks it,
  4. # so this is the one place where its child is run.
  5. use 5.040;
  6. use re '/aasx';
  7. use warnings FATAL => 'all';
  8. use FindBin::libs;
  9. use Test::More;
  10. use File::Temp qw{tempdir};
  11. use File::Path ();
  12. use Time::HiRes ();
  13. our ( $ROOT, $OLDCWD );
  14. BEGIN {
  15. require Cwd;
  16. $OLDCWD = Cwd::getcwd();
  17. $ROOT = tempdir( 'tcms-spawn-XXXXXX', TMPDIR => 1, CLEANUP => 1 );
  18. $ENV{HOME} = $ROOT;
  19. }
  20. require_ok('Trog::DataSource::ProvisionedVirt') or BAIL_OUT("Can't find SUT");
  21. END {
  22. chdir($OLDCWD) if $OLDCWD;
  23. }
  24. {
  25. no warnings qw{once};
  26. $Trog::DataSource::ProvisionedVirt::log_dir = "$ROOT/logs";
  27. }
  28. File::Path::make_path("$ROOT/bin");
  29. my sub provisioner ( $name, $body ) {
  30. my $program = "$ROOT/bin/$name";
  31. open( my $fh, '>', $program ) or die "$program: $!";
  32. print {$fh} "#!/bin/sh\n$body\n";
  33. close($fh) or die "$program: $!";
  34. chmod( 0755, $program ) or die "$program: $!";
  35. return $program;
  36. }
  37. # What the run says once it has stopped, or undef if it has not stopped within
  38. # ten seconds.
  39. my sub finished ($domain) {
  40. foreach ( 1 .. 100 ) {
  41. my $status = Trog::DataSource::ProvisionedVirt::status($domain);
  42. return $status if $status && $status->{state} ne 'running';
  43. Time::HiRes::sleep(0.1);
  44. }
  45. return;
  46. }
  47. my sub run ( $domain, $program, $credentials ) {
  48. my ( $ok, $why ) = Trog::DataSource::ProvisionedVirt::spawn( $domain, $credentials, $ROOT, [ $program, $domain ], 'admin' );
  49. ok( $ok, "a run for $domain starts" ) or diag($why);
  50. return finished($domain);
  51. }
  52. # Starman, under which tPSGI runs, ignores SIGPIPE, and its children inherit
  53. # that. So a write to a provisioner that has gone fails rather than kills.
  54. local $SIG{PIPE} = 'IGNORE';
  55. subtest 'a provisioner that reads its credentials and succeeds' => sub {
  56. my $status = run( 'reads.test.test', provisioner( 'reads', 'cat >/dev/null; exit 0' ), { password => 'hunter2' } );
  57. is( $status->{state}, 'ok', 'the run is ok' );
  58. is( $status->{exit}, '0', 'and exited 0' );
  59. };
  60. subtest 'a provisioner that fails' => sub {
  61. my $status = run( 'fails.test.test', provisioner( 'fails', 'cat >/dev/null; exit 3' ), { password => 'hunter2' } );
  62. is( $status->{state}, 'failed', 'the run failed' );
  63. is( $status->{exit}, '3', 'with what the provisioner exited' );
  64. };
  65. # More than a pipe holds, so the write cannot finish before the provisioner
  66. # has gone, whichever of the two is first.
  67. subtest 'a provisioner that never reads its credentials' => sub {
  68. my $status = run( 'deaf.test.test', provisioner( 'deaf', 'exit 0' ), { password => 'x' x 1_048_576 } );
  69. is( $status->{state}, 'failed', 'the run failed, although the provisioner exited 0' );
  70. my $log = Trog::DataSource::ProvisionedVirt::log_for('deaf.test.test') // '';
  71. like( $log, qr/could[ ]not[ ]write[ ]to[ ]\N*deaf/, 'and the log says that it never got them' );
  72. };
  73. done_testing();