diff --git a/doc/xml/release.xml b/doc/xml/release.xml
index 1fb6fd69f..e672a03a5 100644
--- a/doc/xml/release.xml
+++ b/doc/xml/release.xml
@@ -38,6 +38,14 @@
Remove deprecated archive-max-mb option.
+
+
+
+
+
+
+ Minor changes to Manifest module, mostly for test reproducibility.
+
@@ -66,6 +74,14 @@
Sync time to prevent build failures when running on VirtualBox.
+
+
+
+
+
+
+ Add unit tests for the Manifest module.
+
diff --git a/lib/pgBackRest/Manifest.pm b/lib/pgBackRest/Manifest.pm
index c41926dfb..542846a1a 100644
--- a/lib/pgBackRest/Manifest.pm
+++ b/lib/pgBackRest/Manifest.pm
@@ -308,7 +308,7 @@ sub new
{
if (!defined($strDbVersion))
{
- &log(ASSERT, 'strDbVersion must be provided with bLoad = false');
+ confess &log(ASSERT, 'strDbVersion must be provided with bLoad = false');
}
$self->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, $strDbVersion);
@@ -1113,7 +1113,7 @@ sub buildDefault
my $strMaxValue;
my $iMaxValueTotal = 0;
- foreach my $strValue (keys(%oDefault))
+ foreach my $strValue (sort(keys(%oDefault)))
{
if ($oDefault{$strValue} > $iMaxValueTotal)
{
@@ -1215,7 +1215,7 @@ sub walPath
####################################################################################################################################
# isMasterFile
#
-# Is this file required to copied from the master?
+# Is this file required to be copied from the master?
####################################################################################################################################
sub isMasterFile
{
diff --git a/test/lib/pgBackRestTest/Common/DefineTest.pm b/test/lib/pgBackRestTest/Common/DefineTest.pm
index 7ca6d3454..9d978ba5b 100644
--- a/test/lib/pgBackRestTest/Common/DefineTest.pm
+++ b/test/lib/pgBackRestTest/Common/DefineTest.pm
@@ -588,6 +588,24 @@ my $oTestDef =
},
]
},
+ # Manifest tests
+ {
+ &TESTDEF_NAME => 'manifest',
+
+ &TESTDEF_TEST =>
+ [
+ {
+ &TESTDEF_NAME => 'all',
+ &TESTDEF_TOTAL => 9,
+ &TESTDEF_CONTAINER => true,
+
+ &TESTDEF_COVERAGE =>
+ {
+ 'Manifest' => TESTDEF_COVERAGE_PARTIAL,
+ },
+ },
+ ]
+ },
# Stanza tests
{
&TESTDEF_NAME => 'stanza',
diff --git a/test/lib/pgBackRestTest/Module/Manifest/ManifestAllTest.pm b/test/lib/pgBackRestTest/Module/Manifest/ManifestAllTest.pm
new file mode 100644
index 000000000..b52b66ce8
--- /dev/null
+++ b/test/lib/pgBackRestTest/Module/Manifest/ManifestAllTest.pm
@@ -0,0 +1,1076 @@
+####################################################################################################################################
+# ManifestAllTest.pm - Unit tests for Manifest module
+####################################################################################################################################
+package pgBackRestTest::Module::Manifest::ManifestAllTest;
+use parent 'pgBackRestTest::Env::HostEnvTest';
+
+####################################################################################################################################
+# Perl includes
+####################################################################################################################################
+use strict;
+use warnings FATAL => qw(all);
+use Carp qw(confess);
+
+use File::Basename qw(dirname);
+use Storable qw(dclone);
+
+use pgBackRest::Backup::Common;
+use pgBackRest::Common::Exception;
+use pgBackRest::Common::Ini;
+use pgBackRest::Common::Log;
+use pgBackRest::Common::String;
+use pgBackRest::Common::Wait;
+use pgBackRest::Config::Config;
+use pgBackRest::DbVersion;
+use pgBackRest::Manifest;
+use pgBackRest::Protocol::Helper;
+use pgBackRest::Protocol::Storage::Helper;
+use pgBackRest::Storage::Helper;
+use pgBackRest::Version;
+
+use pgBackRestTest::Common::ContainerTest;
+use pgBackRestTest::Common::ExecuteTest;
+use pgBackRestTest::Common::FileTest;
+use pgBackRestTest::Common::RunTest;
+use pgBackRestTest::Env::Host::HostBackupTest;
+
+####################################################################################################################################
+# Issues / Improvements
+#
+# While working on the unit tests a number of possible issues/improvements have come up. Issues are documented in the tests where
+# they are relevant with an INVESTIGATE tag. General improvements are listed here to be addressed later:
+#
+# 1. Assignment of required values to the manifest is haphazard. Db version is in the constructor but the rest is assigned in
+# Backup.pm. These should probably all be passed in the constructor. It may be like it is now in part because of test needs
+# but that should be changed if so.
+#
+# 2. Improve validate function to check that all values that are collected actually got set. Some things are being checked now,
+# but others like backup-lsn-start, backup-wal-start, etc. are not. Include all options like option-compress, etc. and db info
+# like db-id, system-id, etc. Basically, validate should check everything in the manifest before it is saved (for the last time
+# since intermediate saves may be missing data) and make sure nothing is missing or extraneous. Also check the contents of
+# values, i.e. make sure that checksum is really a 40-character hex string.
+#
+# 3. In fileAdd() the mode should be based on a mask (remove execute bit) of the dir mode, rather than a constant. i.e. dir mode
+# 0700 should become 0600, dir mode 0750 should become 0640, etc.
+####################################################################################################################################
+
+####################################################################################################################################
+# Constants
+####################################################################################################################################
+use constant MODE_0750 => '0750';
+use constant MODE_0700 => '0700';
+use constant MODE_0644 => '0644';
+use constant MODE_0600 => '0600';
+use constant PGCONTROL_SIZE => 8192;
+
+####################################################################################################################################
+# initModule
+####################################################################################################################################
+sub initModule
+{
+ my $self = shift;
+
+ $self->{strDbPath} = $self->testPath() . "/db";
+ $self->{strRepoPath} = $self->testPath() . "/repo";
+ $self->{strArchivePath} = "$self->{strRepoPath}/archive/" . $self->stanza();
+ $self->{strBackupPath} = "$self->{strRepoPath}/backup/" . $self->stanza();
+ $self->{strSpoolPath} = "$self->{strArchivePath}/out";
+ $self->{strExpectedManifest} = $self->testPath() . "/expected.manifest";
+ $self->{strActualManifest} = $self->testPath() . "/actual.manifest";
+}
+
+####################################################################################################################################
+# initTest
+####################################################################################################################################
+sub initTest
+{
+ my $self = shift;
+
+ # Create archive info path
+ storageTest()->pathCreate($self->{strArchivePath}, {bIgnoreExists => true, bCreateParent => true});
+
+ # Create backup info path
+ storageTest()->pathCreate($self->{strBackupPath}, {bIgnoreExists => true, bCreateParent => true});
+
+ # Create pg_control global path
+ storageTest()->pathCreate($self->{strDbPath} . '/' . DB_PATH_GLOBAL, {bCreateParent => true});
+}
+
+####################################################################################################################################
+# manifestCompare
+####################################################################################################################################
+sub manifestCompare
+{
+ my $self = shift;
+ my $oManifestExpected = shift;
+ my $oManifestActual = shift;
+
+ # Remove manifest files if exist
+ storageTest()->remove($self->{strExpectedManifest}, {bIgnoreMissing => true});
+ storageTest()->remove($self->{strActualManifest}, {bIgnoreMissing => true});
+
+ # Section backup. Confirm the copy-start timestamp is set for the actual manifest then copy it to the expected so files can
+ # be compared
+ if ($oManifestActual->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_COPY_START))
+ {
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_COPY_START, undef,
+ $oManifestActual->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_COPY_START));
+ }
+ else
+ {
+ return MANIFEST_KEY_TIMESTAMP_COPY_START . " not set in actual manifest";
+ }
+
+ storageTest()->put($self->{strActualManifest}, iniRender($oManifestActual->{oContent}));
+ storageTest()->put($self->{strExpectedManifest}, iniRender($oManifestExpected->{oContent}));
+
+ return executeTest("diff " . $self->{strExpectedManifest} . " " . $self->{strActualManifest});
+}
+
+####################################################################################################################################
+# run
+####################################################################################################################################
+sub run
+{
+ my $self = shift;
+
+ $self->optionTestSet(CFGOPT_STANZA, $self->stanza());
+ $self->optionTestSet(CFGOPT_REPO_PATH, $self->{strRepoPath});
+ $self->optionTestSet(CFGOPT_DB_PATH, $self->{strDbPath});
+ $self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
+
+ my $strBackupLabel = backupLabelFormat(CFGOPTVAL_BACKUP_TYPE_FULL, undef, 1482000000);
+ my $strBackupPath = storageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
+ my $strBackupManifestFile = "$strBackupPath/" . FILE_MANIFEST;
+ my $iDbCatalogVersion = 201409291;
+
+ my $lTime = time() - 10000;
+ my $strTest = 'test';
+
+ my $oManifestBase = new pgBackRest::Common::Ini($self->{strExpectedManifest}, {bLoad => false});
+
+ # Section: backup:db
+ $oManifestBase->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_94);
+ # Section: target:path
+ my $hDefault = {};
+ $oManifestBase->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_TARGET_PGDATA, undef, $hDefault);
+ $oManifestBase->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_GLOBAL, undef, $hDefault);
+ # Section: target:path:default
+ $oManifestBase->set(MANIFEST_SECTION_TARGET_PATH . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0750);
+ $oManifestBase->set(MANIFEST_SECTION_TARGET_PATH . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestBase->set(MANIFEST_SECTION_TARGET_PATH . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+ # Section backup:target
+ $oManifestBase->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA, MANIFEST_SUBKEY_TYPE, MANIFEST_VALUE_PATH);
+ $oManifestBase->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA, MANIFEST_SUBKEY_PATH, $self->{strDbPath});
+
+ ################################################################################################################################
+ if ($self->begin('new()'))
+ {
+ # Missing DB version
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {(new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false}))}, ERROR_ASSERT,
+ 'strDbVersion must be provided with bLoad = false');
+
+ # Successfully instantiate
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $oManifest = $self->testResult(sub {(new pgBackRest::Manifest($strBackupManifestFile,
+ {bLoad => false, strDbVersion => PG_VERSION_94}))}, "[object]", 'manifest instantiated');
+
+ # Initialize
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->test(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_94) &&
+ $oManifest->test(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, $oManifest->{iInitFormat}) &&
+ $oManifest->test(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $oManifest->{strInitVersion})}, true,
+ ' manifest initialized');
+
+ # Attempt to save without proper path
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {$oManifest->save()}, ERROR_PATH_MISSING,
+ "unable to open '" . $strBackupManifestFile . "': No such file or directory");
+
+ # Create path and save
+ #---------------------------------------------------------------------------------------------------------------------------
+ storageRepo()->pathCreate($strBackupPath);
+ $self->testResult(sub {$oManifest->save()}, "[undef]", 'manifest saved');
+
+ # Load and check the saved file
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $oBackupManifest = $self->testResult(sub {(new pgBackRest::Manifest($strBackupManifestFile))}, "[object]",
+ ' saved manifest loaded');
+ $self->testResult(sub {($oManifest->numericGet(INI_SECTION_BACKREST, INI_KEY_FORMAT) ==
+ $oBackupManifest->numericGet(INI_SECTION_BACKREST, INI_KEY_FORMAT)) &&
+ ($oManifest->get(INI_SECTION_BACKREST, INI_KEY_CHECKSUM) eq
+ $oBackupManifest->get(INI_SECTION_BACKREST, INI_KEY_CHECKSUM))}, true,
+ ' saved manifest equals initial manifest');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('build()'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # Build error if offline = true and no tablespace path
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, false)}, ERROR_FILE_MISSING,
+ "unable to stat '" . $self->{strDbPath} . "/" . MANIFEST_TARGET_PGTBLSPC . "': No such file or directory");
+
+ # bOnline = true tests - Compare the base manifest
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestBase, $oManifest)}, "", 'base manifest');
+
+ # Create expected manifest from base
+ my $oManifestExpected = dclone($oManifestBase);
+
+ # Add global/pg_control file and PG_VERSION file and create a directory with a different modes than default
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_PGCONTROL,
+ {strMode => MODE_0644, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}),
+ $self->controlGenerateContent(PG_VERSION_94));
+
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_PGVERSION,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), PG_VERSION_94);
+
+ # Create base path with different mode than default
+ storageDb()->pathCreate(DB_PATH_BASE, {strMode => MODE_0700});
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0600);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MASTER, undef, true);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_SIZE, PGCONTROL_SIZE);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_MODE, MODE_0644);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' .DB_FILE_PGVERSION,
+ MANIFEST_SUBKEY_SIZE, length(&PG_VERSION_94));
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' .DB_FILE_PGVERSION,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_BASE, MANIFEST_SUBKEY_MODE, MODE_0700);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'paths/files and different modes');
+
+ # Master = false
+ #---------------------------------------------------------------------------------------------------------------------------
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_BASE . '/' . $strTest,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), $strTest);
+
+ # Update expected manifest
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . $strTest,
+ MANIFEST_SUBKEY_MASTER, undef, false);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . $strTest,
+ MANIFEST_SUBKEY_SIZE, length($strTest));
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . $strTest,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'master false');
+
+ # Create a pg_config path and file link
+ my $strConfFile = '/pg_config/postgresql.conf';
+ my $strConfContent = "listen_addresses = *\n";
+ storageDb()->pathCreate('pg_config');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . $strConfFile,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), $strConfContent);
+
+ # link db/pg_config/postgresql.conf.link -> ./postgresql.conf
+ testLinkCreate($self->{strDbPath} . $strConfFile . '.link', './postgresql.conf');
+
+ # INVESTIGATE: on the command line, these links appear to be fine but in the code, a debug line prior to the recursive call to build() produces:
+ # STRPATH BEFORE BUILD: /home/ubuntu/test/test-0/db/base, STRLEVEL PASSED: $VAR1 = 'pg_data/base/pg_config_bad';
+ # STRFILTER: $VAR1 = undef;
+ # STRPATH BEFORE BUILD: /home/ubuntu/test/test-0/db/pg_config, STRLEVEL PASSED: $VAR1 = 'pg_data/base/pg_config_bad/postgresql.conf.link';
+ # STRFILTER: $VAR1 = undef;
+ # STRPATH BEFORE BUILD: /home/ubuntu/test/test-0/db/base/base, STRLEVEL PASSED: $VAR1 = 'pg_data/base/postgresql.conf';
+ # STRFILTER: $VAR1 = undef;
+ #
+ # and right here throws: 'unable to stat '/home/ubuntu/test/test-0/db/base/pg_config/postgresql.conf': No such file or directory'
+ # -- note the extra "base" here - it should just be /home/ubuntu/test/test-0/db/pg_config/postgresql.conf
+ # # link db/base/pg_config_bad -> ../../db/pg_config
+ # testLinkCreate($self->{strDbPath} . '/'. DB_PATH_BASE . '/pg_config_bad', '../../db/pg_config');
+ # # link db/base/postgresql.conf -> ../pg_config/postgresql.conf
+ # testLinkCreate($self->{strDbPath} . '/'. DB_PATH_BASE . '/postgresql.conf', '..' . $strConfFile);
+ # $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_LINK_DESTINATION,
+ # 'TEST THIS');
+
+ # INVESTIGATE: Even though the below code creates a duplicate link, this error occurs (note the pg_config/pg_config):
+ # 'unable to stat '/home/ubuntu/test/test-0/db/pg_config/pg_config/./postgresql.conf': No such file or directory'
+ # ubuntu@pgbackrest-test:~$ ls -l /home/ubuntu/test/test-0/db/pg_config/
+ # total 4
+ # -rw------- 1 ubuntu ubuntu 21 Nov 9 17:07 postgresql.conf
+ # lrwxrwxrwx 1 ubuntu ubuntu 17 Nov 9 19:53 postgresql.conf.bad.link -> ./postgresql.conf
+ # lrwxrwxrwx 1 ubuntu ubuntu 17 Nov 9 19:53 postgresql.conf.link -> ./postgresql.conf
+ # # This link will cause errors because it points to the same location as above
+ # testLinkCreate($self->{strDbPath} . $strConfFile . '.bad.link', './postgresql.conf');
+ #
+ # $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_LINK_DESTINATION,
+ # 'TEST ERROR');
+ # testFileRemove($self->{strDbPath} . $strConfFile . '.link.bad');
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_BASE, MANIFEST_SUBKEY_MODE, MODE_0700);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_TARGET_PGDATA . '/pg_config', undef, $hDefault);
+
+ # Section backup:target
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA . $strConfFile . '.link',
+ MANIFEST_SUBKEY_FILE, 'postgresql.conf');
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA . $strConfFile . '.link',
+ MANIFEST_SUBKEY_PATH, '.');
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA . $strConfFile . '.link',
+ MANIFEST_SUBKEY_TYPE, MANIFEST_VALUE_LINK);
+ # Section target:file
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . $strConfFile,
+ MANIFEST_SUBKEY_SIZE, length($strConfContent));
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . $strConfFile,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ # Section target:link
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_LINK, MANIFEST_TARGET_PGDATA . $strConfFile . '.link',
+ MANIFEST_SUBKEY_DESTINATION, './postgresql.conf');
+ # Section target:link:default
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_LINK . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_LINK . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'link');
+
+ # Test skip files/directories
+ #---------------------------------------------------------------------------------------------------------------------------
+ # Create files to skip
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_POSTGRESQLAUTOCONFTMP ), '');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_BACKUPLABELOLD), '');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_POSTMASTEROPTS), '');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_POSTMASTERPID), '');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_RECOVERYCONF), '');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_RECOVERYDONE), '');
+
+ # Create directories to skip. Add a bogus file to them for test coverage.
+ storageDb()->pathCreate(DB_FILE_PREFIX_TMP);
+ storageDb()->pathCreate('pg_xlog');
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/pg_xlog/' . BOGUS,
+ {strMode => MODE_0644, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGDYNSHMEM);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGDYNSHMEM . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGNOTIFY);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGNOTIFY . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGREPLSLOT);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGREPLSLOT . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGSERIAL);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGSERIAL . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGSNAPSHOTS);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGSNAPSHOTS . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGSTATTMP);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGSTATTMP . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_PATH_PGSUBTRANS);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_PATH_PGSUBTRANS . '/' . BOGUS,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), '');
+ storageDb()->pathCreate(DB_FILE_PGINTERNALINIT);
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_TARGET_PGDATA . '/pg_xlog', undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGDYNSHMEM, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGNOTIFY, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGREPLSLOT, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGSERIAL, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGSNAPSHOTS, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGSTATTMP, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGSUBTRANS, undef, $hDefault);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'skip directories/files');
+
+ # Unskip code path coverage
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $oManifestExpectedUnskip = dclone($oManifestExpected);
+
+ # Change DB version to 93
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_93});
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_93);
+
+ # Update expected manifest
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0600);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+ $oManifestExpectedUnskip->boolSet(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MASTER, undef, true);
+
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGDYNSHMEM . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGDYNSHMEM . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGREPLSLOT . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGREPLSLOT . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpectedUnskip, $oManifest)}, "", 'unskip 94 directories');
+
+ # Change DB version to 91
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_91});
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_91);
+
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSNAPSHOTS . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSNAPSHOTS . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpectedUnskip, $oManifest)}, "", 'unskip 92 directories');
+
+ # Change DB version to 90
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_90});
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_90);
+
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSERIAL . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSERIAL . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpectedUnskip, $oManifest)}, "", 'unskip 91 directories');
+
+ # Change DB version to 84
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_84});
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_84);
+
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGNOTIFY . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGNOTIFY . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpectedUnskip, $oManifest)}, "", 'unskip 90 directories');
+
+ # Change DB version to 83
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_83});
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_83);
+
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSTATTMP . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpectedUnskip->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSTATTMP . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpectedUnskip, $oManifest)}, "", 'unskip 84 directories');
+
+ # Reset Manifest for next tests
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'manifest reset');
+
+ # Tablespaces
+ #---------------------------------------------------------------------------------------------------------------------------
+ # Create pg_tblspc path
+ my $strTblSpcPath = $self->{strDbPath} . '/' . DB_PATH_PGTBLSPC;
+ storageDb()->pathCreate($strTblSpcPath, {bCreateParent => true});
+
+ # Create a directory in pg_tblspc
+ storageDb()->pathCreate("$strTblSpcPath/" . BOGUS, {strMode => '0700'});
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_LINK_EXPECTED,
+ MANIFEST_TARGET_PGTBLSPC . "/" . BOGUS . " is not a symlink - " . DB_PATH_PGTBLSPC . " should contain only symlinks");
+
+ testPathRemove("${strTblSpcPath}/" . BOGUS);
+
+ my $strTblspcId = '99999';
+
+ # Invalid relative tablespace is ../
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", '../');
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_TABLESPACE_IN_PGDATA,
+ 'tablespace symlink ../ destination must not be in $PGDATA');
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Invalid relative tablespace is ..
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", '..');
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_TABLESPACE_IN_PGDATA,
+ 'tablespace symlink .. destination must not be in $PGDATA');
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Invalid relative tablespace is ../base - a subdirectory of $PGDATA
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", '../base');
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_TABLESPACE_IN_PGDATA,
+ 'tablespace symlink ../base destination must not be in $PGDATA');
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Create the catalog key for the tablespace construction
+ $oManifest->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG, undef, $iDbCatalogVersion);
+
+ # Invalid absolute tablespace is $self->{strDbPath} . /base
+ # INVESTIGATE: But this should fail because the link points to a directory in pg_data but instead it passes the
+ # index($hManifest->{$strName}{link_destination}, '/') != 0 and then fails later. It WILL fail "destination must not be in
+ # $PGDATA" if an ending slash is added - so maybe the comment in Manifest.pm "# Make sure that DB_PATH_PGTBLSPC contains
+ # only absolute links that do not point inside PGDATA" is not exactly correct?
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", $self->{strDbPath} . '/base');
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_ASSERT,
+ "tablespace with oid ${strTblspcId} not found in tablespace map\n" .
+ "HINT: was a tablespace created or dropped during the backup?");
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Invalid relative tablespace is ../../BOGUS - which is not in $PGDATA and does not exist
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", '../../' . BOGUS);
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, ERROR_ASSERT,
+ "tablespace with oid ${strTblspcId} not found in tablespace map\n" .
+ "HINT: was a tablespace created or dropped during the backup?");
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Create tablespace directory outside PGDATA
+ my $strTablespace = 'tablespace';
+ storageTest()->pathCreate($strTablespace);
+
+ my $strIntermediateLink = $self->{strDbPath} . "/intermediate_link";
+
+ # Create a link to a link
+ testLinkCreate($strIntermediateLink, $self->testPath() . '/' . $strTablespace);
+ testLinkCreate("${strTblSpcPath}/${strTblspcId}", $strIntermediateLink);
+
+ $self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, false)}, ERROR_LINK_DESTINATION,
+ "link '${strTblSpcPath}/${strTblspcId}' -> '$strIntermediateLink' cannot reference another link");
+
+ testFileRemove($self->{strDbPath} . "/intermediate_link");
+ testFileRemove("${strTblSpcPath}/${strTblspcId}");
+
+ # Reload the manifest otherwise it will contain invalid data from the above exception tests
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # Set the required db catalog version for tablespaces
+ $oManifest->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG, undef, $iDbCatalogVersion);
+ $oManifestExpected->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG, undef, $iDbCatalogVersion);
+
+ # Create a valid symlink pg_tblspc/1 to tablespace/ts1/1 directory
+ my $strTablespaceOid = '1';
+ my $strTablespaceName = "ts${strTablespaceOid}";
+ storageTest()->pathCreate("${strTablespace}/${strTablespaceName}/${strTablespaceOid}", {bCreateParent => true});
+ my $strTablespacePath = storageTest()->pathGet("${strTablespace}/${strTablespaceName}/${strTablespaceOid}");
+ testLinkCreate("${strTblSpcPath}/${strTablespaceOid}", $strTablespacePath);
+
+ # Create the tablespace info in expected manifest
+ my $strMfTs = MANIFEST_TARGET_PGTBLSPC . "/" . $strTablespaceOid;
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, $strMfTs, MANIFEST_SUBKEY_PATH, $strTablespacePath);
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, $strMfTs, MANIFEST_SUBKEY_TABLESPACE_ID, $strTablespaceOid);
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, $strMfTs, MANIFEST_SUBKEY_TABLESPACE_NAME, $strTablespaceName);
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_TARGET, $strMfTs, MANIFEST_SUBKEY_TYPE, MANIFEST_VALUE_LINK);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_LINK, MANIFEST_TARGET_PGDATA . "/${strMfTs}", MANIFEST_SUBKEY_DESTINATION,
+ $strTablespacePath);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_TARGET_PGTBLSPC, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, MANIFEST_PATH_PGTBLSPC, undef, $hDefault);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_PATH, $strMfTs, undef, $hDefault);
+
+ # In offline mode, do not skip the db WAL path
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_MODE, MODE_0644);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/pg_xlog/' . BOGUS,
+ MANIFEST_SUBKEY_MODE, MODE_0644);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/pg_xlog/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/pg_xlog/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, false);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'offline with valid tablespace - do not skip database WAL directory');
+
+ # Create tablespace and database maps
+ my $hTablespaceMap = {};
+ $hTablespaceMap->{$strTablespaceOid} = $strTablespaceName;
+
+ my $hDatabaseMap = {};
+ $hDatabaseMap->{&BOGUS}{&MANIFEST_KEY_DB_ID} = 12345;
+ $hDatabaseMap->{&BOGUS}{&MANIFEST_KEY_DB_LAST_SYSTEM_ID} = 67890;
+
+ $oManifestExpected->numericSet(MANIFEST_SECTION_DB, BOGUS, MANIFEST_KEY_DB_ID, $hDatabaseMap->{&BOGUS}{&MANIFEST_KEY_DB_ID});
+ $oManifestExpected->numericSet(MANIFEST_SECTION_DB, BOGUS, MANIFEST_KEY_DB_LAST_SYSTEM_ID,
+ $hDatabaseMap->{&BOGUS}{&MANIFEST_KEY_DB_LAST_SYSTEM_ID});
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, false, $hTablespaceMap, $hDatabaseMap);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'offline passing tablespace map and database map');
+
+ # Reload the manifest with version < 9.0
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_84});
+
+ # Catalog not stored in < 9.0
+ $oManifestExpected->remove(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG);
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION, undef, PG_VERSION_84);
+
+ # Add unskip directories
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGDYNSHMEM . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGDYNSHMEM . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGREPLSLOT . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGREPLSLOT . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSNAPSHOTS . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSNAPSHOTS . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSERIAL . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGSERIAL . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGNOTIFY . '/' . BOGUS,
+ MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_PATH_PGNOTIFY . '/' . BOGUS,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, false, $hTablespaceMap, $hDatabaseMap);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'tablespace with version < 9.0');
+
+ # Undefined user/group
+ #---------------------------------------------------------------------------------------------------------------------------
+ executeTest("sudo chgrp 777 " . $self->{strDbPath} . '/pg_xlog/' . BOGUS);
+ executeTest("sudo chown 777 " . $self->{strDbPath} . '/pg_xlog/' . BOGUS);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/pg_xlog/' . BOGUS,
+ MANIFEST_SUBKEY_GROUP, false);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/pg_xlog/' . BOGUS,
+ MANIFEST_SUBKEY_USER, false);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, false);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'undefined user/group');
+
+ # Reset the group / owner
+ executeTest("sudo chgrp " . TEST_GROUP . " " . $self->{strDbPath} . '/pg_xlog/' . BOGUS);
+ executeTest("sudo chown " . TEST_USER . " " . $self->{strDbPath} . '/pg_xlog/' . BOGUS);
+ }
+
+ ################################################################################################################################
+ if ($self->begin('get/set'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # SubKey required but has not been set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {$oManifest->boolGet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_CHECKSUM_PAGE)},
+ ERROR_ASSERT, "strSection '" . MANIFEST_SECTION_TARGET_FILE . "', strKey '" . BOGUS . "', strSubKey '" .
+ MANIFEST_SUBKEY_CHECKSUM_PAGE . "' is required but not defined");
+
+ # SubKey not required and not set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->boolGet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_CHECKSUM_PAGE, false)},
+ false, 'boolGet() - false');
+
+ # Set and get a boolean value
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->boolSet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_CHECKSUM_PAGE, true);
+ $self->testResult(sub {$oManifest->boolGet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_CHECKSUM_PAGE)}, true,
+ 'boolGet() - true');
+
+ # Get default boolean value
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, false, true)},
+ true, 'boolGet() - default true');
+
+ # Get but don't return a default boolean value even if there is supposed to be one
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, false, false)},
+ false, 'boolGet() - default false');
+
+ # Get default numeric value
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_SIZE, false, 0)},
+ 0, 'numericGet() - default 0');
+
+ # Coverage for $self->SUPER::get("${strSection}:default"
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->get(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_USER, false)},
+ "[undef]", 'get() - default section');
+
+ # Get the correct path for the control file in the DB
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA, MANIFEST_SUBKEY_PATH, $self->{strDbPath});
+ $self->testResult(sub {$oManifest->dbPathGet($oManifest->get(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA,
+ MANIFEST_SUBKEY_PATH), MANIFEST_FILE_PGCONTROL)},
+ $self->{strDbPath} . "/" . DB_FILE_PGCONTROL, 'dbPathGet() - control file');
+
+ # Get filename - no path passed
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->dbPathGet(undef, BOGUS)}, BOGUS, 'dbPathGet() - filename');
+
+ # repoPathGet - no tablespace
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->repoPathGet(MANIFEST_TARGET_PGDATA, DB_FILE_PGCONTROL)},
+ MANIFEST_TARGET_PGDATA . "/" . DB_FILE_PGCONTROL, 'repoPathGet() - pg_control');
+
+ # repoPathGet - tablespace - no subpath
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $strTablespaceId = "1";
+ my $strTablespace = MANIFEST_TARGET_PGTBLSPC . "/" . $strTablespaceId;
+ my $strTablespaceName = "ts" . $strTablespaceId;
+
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, $strTablespace, MANIFEST_SUBKEY_PATH, $self->{strDbPath} .
+ "/tablespace/" . $strTablespaceName);
+ $self->testResult(sub {$oManifest->repoPathGet($strTablespace)}, $strTablespace,
+ 'repoPathGet() - tablespace - no tablepace-id nor subpath');
+
+ # repoPathGet - fully qualified tablespace target
+ #---------------------------------------------------------------------------------------------------------------------------
+ # Set the catalog for the DB since that is what is expected to be returned
+ $oManifest->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG, undef, $iDbCatalogVersion);
+ $self->testResult(sub {$oManifest->tablespacePathGet()}, "PG_" . PG_VERSION_94 . "_" . $iDbCatalogVersion,
+ 'tablespacePathGet()');
+
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, $strTablespace, MANIFEST_SUBKEY_TABLESPACE_ID, $strTablespaceId);
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, $strTablespace, MANIFEST_SUBKEY_TABLESPACE_NAME, $strTablespaceName);
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, $strTablespace, MANIFEST_SUBKEY_TYPE, MANIFEST_VALUE_LINK);
+
+ $self->testResult(sub {$oManifest->repoPathGet($strTablespace, BOGUS)}, $strTablespace . "/PG_" . PG_VERSION_94 . "_" .
+ $iDbCatalogVersion . "/" . BOGUS, 'repoPathGet() - tablespace valid with subpath');
+
+ # isTargetLink
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->isTargetLink(MANIFEST_TARGET_PGDATA)}, false, "isTargetLink - false");
+ $self->testResult(sub {$oManifest->isTargetLink($strTablespace)}, true, "isTargetLink - true");
+
+ # isTargetLink
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testResult(sub {$oManifest->isTargetFile(MANIFEST_TARGET_PGDATA)}, false, "isTargetFile - false");
+
+ $oManifest->set(MANIFEST_SECTION_BACKUP_TARGET, MANIFEST_TARGET_PGDATA, MANIFEST_SUBKEY_FILE, BOGUS);
+ $self->testResult(sub {$oManifest->isTargetFile(MANIFEST_TARGET_PGDATA)}, true, "isTargetFile - true");
+ }
+
+ ################################################################################################################################
+ if ($self->begin('isTarget - exceptions'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # Target not defined
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {$oManifest->isTargetValid()}, ERROR_ASSERT, 'target is not defined');
+ $self->testException(sub {$oManifest->isTargetFile()}, ERROR_ASSERT, 'target is not defined');
+ $self->testException(sub {$oManifest->isTargetLink()}, ERROR_ASSERT, 'target is not defined');
+ $self->testException(sub {$oManifest->isTargetTablespace()}, ERROR_ASSERT, 'target is not defined');
+
+ # Target not valid
+ #---------------------------------------------------------------------------------------------------------------------------
+ $self->testException(sub {$oManifest->isTargetValid(BOGUS, true)}, ERROR_ASSERT, BOGUS . ' is not a valid target');
+ $self->testResult(sub {$oManifest->isTargetValid(BOGUS, false)}, false, 'isTargetValid - bError = false, return false');
+ $self->testResult(sub {$oManifest->isTargetValid(BOGUS)}, false, 'isTargetValid - bError = undef, false');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('dbVerion(), xactPath(), walPath()'))
+ {
+ # dbVersion, xactPath and walPath - PG < 10
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ $self->testResult(sub {$oManifest->dbVersion()}, PG_VERSION_94, 'dbVersion < 10');
+ $self->testResult(sub {$oManifest->xactPath()}, 'pg_clog', ' xactPath - pg_clog');
+ $self->testResult(sub {$oManifest->walPath()}, 'pg_xlog', ' walPath - pg_xlog');
+
+ # dbVersion, xactPath and walPath - PG >= 10
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_10});
+
+ $self->testResult(sub {$oManifest->dbVersion()}, PG_VERSION_10, 'dbVersion >= 10');
+ $self->testResult(sub {$oManifest->xactPath()}, 'pg_xact', ' xactPath - pg_xact');
+ $self->testResult(sub {$oManifest->walPath()}, 'pg_wal', ' walPath - pg_wal');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('validate()'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # Set a target:file with only a timestamp - fail size not set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->numericSet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_TIMESTAMP, 1509384645);
+ $self->testException(sub {$oManifest->validate()}, ERROR_ASSERT,
+ "manifest subvalue 'size' not set for file '" . BOGUS . "'");
+
+ # Set target:file size - fail checksum not set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->numericSet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_SIZE, 1);
+ $self->testException(sub {$oManifest->validate()}, ERROR_ASSERT,
+ "manifest subvalue 'checksum' not set for file '" . BOGUS . "'");
+
+ # Set target:file checksum - validate passes when size > 0 and checksum set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->numericSet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_CHECKSUM, 0);
+ $self->testResult(sub {$oManifest->validate()}, "[undef]", 'manifest validated - size 1, checksum 0');
+
+ # Set target:file size to 0 - validate passes when size 0
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oManifest->numericSet(MANIFEST_SECTION_TARGET_FILE, BOGUS, MANIFEST_SUBKEY_SIZE, 0);
+ $self->testResult(sub {$oManifest->validate()}, "[undef]", 'manifest validated - size 0');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('future file and last manifest'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ # Create expected manifest from base
+ my $oManifestExpected = dclone($oManifestBase);
+
+ # Future timestamp on file
+ #---------------------------------------------------------------------------------------------------------------------------
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . $strTest,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime + 20000}), $strTest);
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0600);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MASTER, undef, true);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_SIZE,
+ length($strTest));
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_TIMESTAMP,
+ $lTime + 20000);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_FUTURE, 'y');
+
+ $self->testResult(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, true)}, "[undef]",
+ 'future timestamp warning', {strLogExpect =>
+ "WARN: some files have timestamps in the future - they will be copied to prevent possible race conditions"});
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'manifest future subkey=y');
+
+ # Future timestamp in last manifest
+ #---------------------------------------------------------------------------------------------------------------------------
+ my $oLastManifest = dclone($oManifestExpected);
+
+ # Set a backup label
+ $oLastManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL, undef, BOGUS);
+ $oManifestExpected->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_PRIOR, undef, BOGUS);
+
+ # Remove the file and recreate it without it being in the future
+ storageTest()->remove($self->{strDbPath} . '/' . $strTest);
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . $strTest,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), $strTest);
+
+ # Remove the future subkey from the expected manifest and reset the timestamp
+ $oManifestExpected->remove(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_FUTURE);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_TIMESTAMP,
+ $lTime);
+
+ # Create a new manifest
+ $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+
+ $self->testResult(sub {$oManifest->build(storageDb(), $self->{strDbPath}, $oLastManifest, true)}, "[undef]",
+ 'last manifest future timestamp warning', {strLogExpect =>
+ "WARN: some files have timestamps in the future - they will be copied to prevent possible race conditions"});
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'last manifest future subkey=y, new manifest future subkey removed');
+
+ # File info in last manifest same as current
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oLastManifest->remove(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_FUTURE);
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_TIMESTAMP,
+ $lTime);
+
+ # Update reference in expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest, MANIFEST_SUBKEY_REFERENCE,
+ BOGUS);
+
+ # Check reference
+ $oManifest->build(storageDb(), $self->{strDbPath}, $oLastManifest, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'reference set to prior backup label');
+
+ # Create a new file reference
+ my $strTestNew = $strTest . 'new';
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . $strTestNew,
+ {strMode => MODE_0600, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTime}), $strTestNew);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_SIZE, length($strTestNew));
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_SIZE, length($strTestNew));
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_TIMESTAMP, $lTime);
+
+ # Set a reference, checksum, repo size, master and page checksum in the last manifest
+ my $strCheckSum = '1234567890';
+ my $lRepoSize = 10000;
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_REFERENCE, BOGUS . BOGUS);
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM, $strCheckSum);
+ $oLastManifest->numericSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_REPO_SIZE, $lRepoSize);
+ $oLastManifest->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_MASTER, false);
+ $oLastManifest->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE, true);
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_REFERENCE, BOGUS . BOGUS);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM, $strCheckSum);
+ $oManifestExpected->numericSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_REPO_SIZE, $lRepoSize);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE, true);
+
+ # Default "master" is flipping because it's not something we read from disk
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MASTER, undef, false);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTest,
+ MANIFEST_SUBKEY_MASTER, true);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, $oLastManifest, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'updates from last manifest');
+
+ # MANIFEST_SUBKEY_CHECKSUM_PAGE = false and MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR set/not set
+ #---------------------------------------------------------------------------------------------------------------------------
+ $oLastManifest->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE, false);
+
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE, false);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, $oLastManifest, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'checksum-page false, checksum-page-error not set');
+
+ my @iyChecksumPageError = (1);
+ $oLastManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR, \@iyChecksumPageError);
+
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . $strTestNew,
+ MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR, \@iyChecksumPageError);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, $oLastManifest, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'checksum-page false, checksum-page-error set');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('fileAdd()'))
+ {
+ my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94});
+ my $oManifestExpected = dclone($oManifestBase);
+
+ $oManifest->build(storageDb(), $self->{strDbPath}, undef, true);
+
+ # Add a file after building manifest
+ my $lTimeTest = $lTime + 10;
+ storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . $strTest,
+ {strMode => MODE_0644, strUser => TEST_USER, strGroup => TEST_GROUP, lTimestamp => $lTimeTest}), $strTest);
+
+ $oManifest->fileAdd($strTest, $lTimeTest, 0, 0, true);
+
+ # Update expected manifest
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE, MODE_0600);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_USER, TEST_USER);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_GROUP, TEST_GROUP);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_TIMESTAMP, $lTimeTest);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_SIZE, 0);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_CHECKSUM, 0);
+ $oManifestExpected->boolSet(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MASTER, true);
+
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "", 'file added to manifest');
+
+ # Remove the file user, mode and group from the actual and expected manifest
+ $oManifestExpected->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE);
+ $oManifestExpected->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_USER);
+ $oManifestExpected->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_GROUP);
+
+ $oManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE);
+ $oManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_USER);
+ $oManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_GROUP);
+
+ # Add a target:file:default section to the manifests with a mode other than 0600
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0750);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+
+ $oManifest->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0750);
+ $oManifest->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_GROUP, undef, TEST_GROUP);
+ $oManifest->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_USER, undef, TEST_USER);
+
+ # Set the expected mode to 0600
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE, MODE_0600);
+
+ $oManifest->fileAdd($strTest, $lTimeTest, 0, 0, true);
+
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'file added to manifest - file:default values set');
+
+ # Remove the file mode from the manifests and change the default so it need not be set for the file
+ $oManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE);
+ $oManifest->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0600);
+
+ $oManifestExpected->remove(MANIFEST_SECTION_TARGET_FILE, $strTest, MANIFEST_SUBKEY_MODE);
+ $oManifestExpected->set(MANIFEST_SECTION_TARGET_FILE . ":default", MANIFEST_SUBKEY_MODE, undef, MODE_0600);
+
+ $oManifest->fileAdd($strTest, $lTimeTest, 0, 0, true);
+ $self->testResult(sub {$self->manifestCompare($oManifestExpected, $oManifest)}, "",
+ 'file added to manifest - default mode set 0600');
+ }
+
+ ################################################################################################################################
+ if ($self->begin('isChecksumPage()'))
+ {
+ my $strFile = BOGUS;
+
+ $self->testResult(sub {isChecksumPage($strFile)}, false, "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . DB_FILE_PGVERSION;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . DB_FILE_PGINTERNALINIT;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE. '/' . DB_FILE_PGFILENODEMAP;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGTBLSPC . '/' . DB_FILE_PGFILENODEMAP;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGTBLSPC . '/' . DB_FILE_PGINTERNALINIT;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGTBLSPC . '/' . DB_FILE_PGVERSION;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_GLOBAL. '/' . DB_FILE_PGFILENODEMAP;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_GLOBAL. '/' . DB_FILE_PGINTERNALINIT;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_GLOBAL. '/' . DB_FILE_PGVERSION;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_GLOBAL. '/' . DB_FILE_PGCONTROL;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_FILE_PGCONTROL;
+ $self->testResult(sub {isChecksumPage($strFile)}, false,
+ "file '${strFile}' isChecksumPage=false");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_BASE . '/' . BOGUS;
+ $self->testResult(sub {isChecksumPage($strFile)}, true,
+ "file '${strFile}' isChecksumPage=true");
+
+ $strFile = MANIFEST_TARGET_PGDATA . '/' . DB_PATH_GLOBAL . '/' . BOGUS;
+ $self->testResult(sub {isChecksumPage($strFile)}, true,
+ "file '${strFile}' isChecksumPage=true");
+ }
+}
+
+1;