1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-04-21 11:57:01 +02:00
pgbackrest/test/lib/pgBackRestTest/Module/Common/CommonIoHandlePerlTest.pm
David Steele d211c2b8b5 Fix possible truncated WAL segments when an error occurs mid-write.
The file write object destructors called close() and finalized the file even if it was not completely written.  This was an issue in both the C and Perl code.

Rewrite the destructors to simply free resources (like file handles) rather than calling the close() method.  This leaves the temp file in place for filesystems that use temp files.

Add unit tests to prevent regression.

Reported by blogh.
2019-02-15 11:52:39 +02:00

210 lines
11 KiB
Perl

####################################################################################################################################
# Tests for Common::Io::Handle module
####################################################################################################################################
package pgBackRestTest::Module::Common::CommonIoHandlePerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Fcntl qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC O_NONBLOCK);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Io::Handle;
use pgBackRest::Common::Log;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test data
my $strFile = $self->testPath() . qw{/} . 'file.txt';
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
my $iFileLengthHalf = int($iFileLength / 2);
################################################################################################################################
if ($self->begin('new() & handleRead() & handleReadSet() & handleWrite() & handleWriteSet()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', undef, undef)}, '[object]', 'new - no handles');
$self->testResult(sub {$oIoHandle->id()}, 'test', ' check error msg');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oIoHandle->handleReadSet(1)}, 1, ' set read handle');
$self->testResult(sub {$oIoHandle->handleRead()}, 1, ' check read handle');
$self->testResult(sub {$oIoHandle->handleWrite()}, undef, ' check write handle');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oIoHandle->handleWriteSet(2)}, 2, ' set write handle');
$self->testResult(sub {$oIoHandle->handleRead()}, 1, ' check read handle');
$self->testResult(sub {$oIoHandle->handleWrite()}, 2, ' check write handle');
#---------------------------------------------------------------------------------------------------------------------------
$oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', 1, undef)}, '[object]', 'new - read handle');
$self->testResult(sub {$oIoHandle->handleRead()}, 1, ' check read handle');
$self->testResult(sub {$oIoHandle->handleWrite()}, undef, ' check write handle');
#---------------------------------------------------------------------------------------------------------------------------
$oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', undef, 2)}, '[object]', 'new - write handle');
$self->testResult(sub {$oIoHandle->handleRead()}, undef, ' check read handle');
$self->testResult(sub {$oIoHandle->handleWrite()}, 2, ' check write handle');
#---------------------------------------------------------------------------------------------------------------------------
$oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', 1, 2)}, '[object]', 'new - read/write handle');
$self->testResult(sub {$oIoHandle->handleRead()}, 1, ' check read handle');
$self->testResult(sub {$oIoHandle->handleWrite()}, 2, ' check write handle');
}
################################################################################################################################
if ($self->begin('read()'))
{
my $tContent;
my $fhRead;
#---------------------------------------------------------------------------------------------------------------------------
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
sysopen($fhRead, $strFile, O_RDONLY)
or confess &log(ERROR, "unable to open '${strFile}'");
my $oIoHandle = $self->testResult(sub {new pgBackRest::Common::Io::Handle("'$strFile'", $fhRead)}, '[object]', 'open');
$self->testException(
sub {$oIoHandle->read(\$tContent, -1)}, ERROR_FILE_READ, "unable to read from '${strFile}': Negative length");
#---------------------------------------------------------------------------------------------------------------------------
$tContent = undef;
sysopen($fhRead, $strFile, O_RDONLY)
or confess &log(ERROR, "unable to open '${strFile}'");
$oIoHandle = $self->testResult(sub {new pgBackRest::Common::Io::Handle("'$strFile'", $fhRead)}, '[object]', 'open');
$self->testResult(sub {$oIoHandle->read(\$tContent, $iFileLengthHalf)}, $iFileLengthHalf, ' read part 1');
$self->testResult($tContent, substr($strFileContent, 0, $iFileLengthHalf), ' check read');
$self->testResult(sub {$oIoHandle->eof()}, false, ' not eof');
$self->testResult(
sub {$oIoHandle->read(
\$tContent, $iFileLength - $iFileLengthHalf)}, $iFileLength - $iFileLengthHalf, ' read part 2');
$self->testResult($tContent, $strFileContent, ' check read');
$self->testResult(sub {$oIoHandle->read(\$tContent, 1)}, 0, ' zero read');
$self->testResult(sub {$oIoHandle->eof()}, true, ' eof');
}
################################################################################################################################
if ($self->begin('write()'))
{
my $tContent;
my $fhRead;
my $fhWrite;
#---------------------------------------------------------------------------------------------------------------------------
sysopen($fhWrite, $strFile, O_WRONLY | O_CREAT | O_TRUNC)
or confess &log(ERROR, "unable to open '${strFile}'");
my $oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle("'$strFile'", undef, $fhWrite)}, '[object]', 'open write');
$self->testException(
sub {$oIoHandle->write(undef)}, ERROR_FILE_WRITE,
"unable to write to '${strFile}': Can't use an undefined value as a SCALAR reference");
#---------------------------------------------------------------------------------------------------------------------------
$tContent = substr($strFileContent, 0, $iFileLengthHalf);
$self->testResult(sub {$oIoHandle->write(\$tContent)}, $iFileLengthHalf, ' write part 1');
$self->testResult(sub {$oIoHandle->size()}, $iFileLengthHalf, ' check part 1 size');
$tContent = substr($strFileContent, $iFileLengthHalf);
$self->testResult(sub {$oIoHandle->write(\$tContent)}, $iFileLength - $iFileLengthHalf, ' write part 2');
$self->testResult(sub {$oIoHandle->size()}, $iFileLength, ' check part 2 size');
$self->testResult(sub {$oIoHandle->close()}, true, ' close');
sysopen($fhRead, $strFile, O_RDONLY)
or confess &log(ERROR, "unable to open '${strFile}'");
$oIoHandle = $self->testResult(sub {new pgBackRest::Common::Io::Handle("'$strFile'", $fhRead)}, '[object]', 'open read');
$tContent = undef;
$self->testResult(sub {$oIoHandle->read(\$tContent, $iFileLength)}, $iFileLength, ' read');
$self->testResult($tContent, $strFileContent, ' check write content');
}
################################################################################################################################
if ($self->begin('result() & resultSet()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', undef, undef)}, '[object]', 'new - no handles');
$self->testResult(sub {$oIoHandle->resultSet('Module::1', 1)}, 1, ' set int result');
$self->testResult(sub {$oIoHandle->resultSet('Module::2', {value => 2})}, '{value => 2}', ' set hash result');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oIoHandle->result('Module::1')}, 1, ' check int result');
$self->testResult(sub {$oIoHandle->result('Module::2')}, '{value => 2}', ' check hash result');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oIoHandle->{rhResult}}, '{Module::1 => 1, Module::2 => {value => 2}}', ' check all results');
}
################################################################################################################################
if ($self->begin('className()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oIoHandle = $self->testResult(
sub {new pgBackRest::Common::Io::Handle('test', undef, undef)}, '[object]', 'new - no handles');
$self->testResult(sub {$oIoHandle->className()}, COMMON_IO_HANDLE, ' check class name');
}
################################################################################################################################
if ($self->begin('close()'))
{
my $tContent;
my $fhRead;
#---------------------------------------------------------------------------------------------------------------------------
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
sysopen($fhRead, $strFile, O_RDONLY)
or confess &log(ERROR, "unable to open '${strFile}'");
my $oIoHandle = $self->testResult(sub {new pgBackRest::Common::Io::Handle("'$strFile'", $fhRead)}, '[object]', 'open read');
$self->testResult(sub {$oIoHandle->read(\$tContent, $iFileLengthHalf)}, $iFileLengthHalf, ' read');
$self->testResult(sub {$oIoHandle->close()}, true, ' close');
$self->testResult(sub {$oIoHandle->close()}, true, ' close again');
$self->testResult(sub {$oIoHandle->result(COMMON_IO_HANDLE)}, $iFileLengthHalf, ' check result');
# Destroy and to make sure close runs again
undef($oIoHandle);
}
}
1;