mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2025-02-03 14:01:57 +02:00
Start working on testgres framework for pg_probackup.
This commit is contained in:
parent
6642f27cca
commit
e7d6a08bf3
@ -827,7 +827,7 @@ parse_pair(const char buffer[], char key[], char value[])
|
||||
if (end - start <= 0)
|
||||
{
|
||||
if (*start == '=')
|
||||
elog(WARNING, "syntax error in \"%s\"", buffer);
|
||||
elog(ERROR, "syntax error in \"%s\"", buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -841,7 +841,7 @@ parse_pair(const char buffer[], char key[], char value[])
|
||||
|
||||
if (*start != '=')
|
||||
{
|
||||
elog(WARNING, "syntax error in \"%s\"", buffer);
|
||||
elog(ERROR, "syntax error in \"%s\"", buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -858,7 +858,7 @@ parse_pair(const char buffer[], char key[], char value[])
|
||||
|
||||
if (*start != '\0' && *start != '#')
|
||||
{
|
||||
elog(WARNING, "syntax error in \"%s\"", buffer);
|
||||
elog(ERROR, "syntax error in \"%s\"", buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
12
tests/__init__.py
Normal file
12
tests/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
import unittest
|
||||
|
||||
from . import init_test, option_test, show_test
|
||||
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTests(loader.loadTestsFromModule(init_test))
|
||||
suite.addTests(loader.loadTestsFromModule(option_test))
|
||||
suite.addTests(loader.loadTestsFromModule(show_test))
|
||||
|
||||
return suite
|
51
tests/expected/option_help.out
Normal file
51
tests/expected/option_help.out
Normal file
@ -0,0 +1,51 @@
|
||||
pg_probackup manage backup/recovery of PostgreSQL database.
|
||||
|
||||
Usage:
|
||||
pg_probackup [option...] init
|
||||
pg_probackup [option...] backup
|
||||
pg_probackup [option...] restore
|
||||
pg_probackup [option...] show [backup-ID]
|
||||
pg_probackup [option...] validate backup-ID
|
||||
pg_probackup [option...] delete backup-ID
|
||||
pg_probackup [option...] delwal [backup-ID]
|
||||
|
||||
Common Options:
|
||||
-B, --backup-path=PATH location of the backup storage area
|
||||
-D, --pgdata=PATH location of the database storage area
|
||||
|
||||
Backup options:
|
||||
-b, --backup-mode=MODE backup mode (full, page, ptrack)
|
||||
-C, --smooth-checkpoint do smooth checkpoint before backup
|
||||
--stream stream the transaction log and include it in the backup
|
||||
-S, --slot=SLOTNAME replication slot to use
|
||||
--backup-pg-log backup of pg_log directory
|
||||
-j, --threads=NUM number of parallel threads
|
||||
--progress show progress
|
||||
|
||||
Restore options:
|
||||
--time time stamp up to which recovery will proceed
|
||||
--xid transaction ID up to which recovery will proceed
|
||||
--inclusive whether we stop just after the recovery target
|
||||
--timeline recovering into a particular timeline
|
||||
-j, --threads=NUM number of parallel threads
|
||||
--progress show progress
|
||||
|
||||
Delete options:
|
||||
--wal remove unnecessary wal files
|
||||
|
||||
Connection options:
|
||||
-d, --dbname=DBNAME database to connect
|
||||
-h, --host=HOSTNAME database server host or socket directory
|
||||
-p, --port=PORT database server port
|
||||
-U, --username=USERNAME user name to connect as
|
||||
-w, --no-password never prompt for password
|
||||
-W, --password force password prompt
|
||||
|
||||
Generic options:
|
||||
-q, --quiet don't write any messages
|
||||
-v, --verbose verbose mode
|
||||
--help show this help, then exit
|
||||
--version output version information and exit
|
||||
|
||||
Read the website for details. <https://github.com/postgrespro/pg_probackup>
|
||||
Report bugs to <https://github.com/postgrespro/pg_probackup/issues>.
|
1
tests/expected/option_version.out
Normal file
1
tests/expected/option_version.out
Normal file
@ -0,0 +1 @@
|
||||
pg_probackup 1.0
|
41
tests/init_test.py
Normal file
41
tests/init_test.py
Normal file
@ -0,0 +1,41 @@
|
||||
import unittest
|
||||
import os
|
||||
from os import path
|
||||
import six
|
||||
from .pb_lib import dir_files, ProbackupTest
|
||||
|
||||
|
||||
class InitTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InitTest, self).__init__(*args, **kwargs)
|
||||
|
||||
def test_success_1(self):
|
||||
"""Success normal init"""
|
||||
node = self.make_bnode('test_success_1', base_dir="tmp_dirs/init/success_1")
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
self.assertEqual(
|
||||
dir_files(self.backup_dir(node)),
|
||||
['backups', 'pg_probackup.conf', 'wal']
|
||||
)
|
||||
|
||||
def test_already_exist_2(self):
|
||||
"""Failure with backup catalog already existed"""
|
||||
node = self.make_bnode('test_already_exist_2', base_dir="tmp_dirs/init/already_exist_2")
|
||||
self.init_pb(node)
|
||||
self.assertEqual(
|
||||
self.init_pb(node),
|
||||
six.b("ERROR: backup catalog already exist and it's not empty\n")
|
||||
)
|
||||
|
||||
def test_abs_path_3(self):
|
||||
"""failure with backup catalog should be given as absolute path"""
|
||||
node = self.make_bnode('test_abs_path_3', base_dir="tmp_dirs/init/abs_path_3")
|
||||
self.assertEqual(
|
||||
self.run_pb(["init", "-B", path.relpath("%s/backup" % node.base_dir, self.dir_path)]),
|
||||
six.b("ERROR: -B, --backup-path must be an absolute path\n")
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
118
tests/option_test.py
Normal file
118
tests/option_test.py
Normal file
@ -0,0 +1,118 @@
|
||||
import unittest
|
||||
from os import path
|
||||
import six
|
||||
from .pb_lib import ProbackupTest
|
||||
from testgres import stop_all
|
||||
|
||||
|
||||
class OptionTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OptionTest, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
stop_all()
|
||||
|
||||
def test_help_1(self):
|
||||
"""help options"""
|
||||
with open(path.join(self.dir_path, "expected/option_help.out"), "rb") as help_out:
|
||||
self.assertEqual(
|
||||
self.run_pb(["--help"]),
|
||||
help_out.read()
|
||||
)
|
||||
|
||||
def test_version_2(self):
|
||||
"""help options"""
|
||||
with open(path.join(self.dir_path, "expected/option_version.out"), "rb") as version_out:
|
||||
self.assertEqual(
|
||||
self.run_pb(["--version"]),
|
||||
version_out.read()
|
||||
)
|
||||
|
||||
def test_without_backup_path_3(self):
|
||||
"""backup command failure without backup mode option"""
|
||||
self.assertEqual(
|
||||
self.run_pb(["backup", "-b", "full"]),
|
||||
six.b("ERROR: required parameter not specified: BACKUP_PATH (-B, --backup-path)\n")
|
||||
)
|
||||
|
||||
def test_options_4(self):
|
||||
node = self.make_bnode('test_options_4', base_dir="tmp_dirs/option/option_common")
|
||||
try:
|
||||
node.stop()
|
||||
except:
|
||||
pass
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
# backup command failure without backup mode option
|
||||
self.assertEqual(
|
||||
self.run_pb(["backup", "-B", self.backup_dir(node), "-D", node.data_dir]),
|
||||
six.b("ERROR: Required parameter not specified: BACKUP_MODE (-b, --backup-mode)\n")
|
||||
)
|
||||
|
||||
# backup command failure with invalid backup mode option
|
||||
self.assertEqual(
|
||||
self.run_pb(["backup", "-b", "bad", "-B", self.backup_dir(node)]),
|
||||
six.b('ERROR: invalid backup-mode "bad"\n')
|
||||
)
|
||||
|
||||
# delete failure without ID
|
||||
self.assertEqual(
|
||||
self.run_pb(["delete", "-B", self.backup_dir(node)]),
|
||||
six.b("ERROR: required backup ID not specified\n")
|
||||
)
|
||||
|
||||
node.start()
|
||||
|
||||
# syntax error in pg_probackup.conf
|
||||
with open(path.join(self.backup_dir(node), "pg_probackup.conf"), "a") as conf:
|
||||
conf.write(" = INFINITE\n")
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node),
|
||||
six.b('ERROR: syntax error in " = INFINITE"\n')
|
||||
)
|
||||
|
||||
self.clean_pb(node)
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
# invalid value in pg_probackup.conf
|
||||
with open(path.join(self.backup_dir(node), "pg_probackup.conf"), "a") as conf:
|
||||
conf.write("BACKUP_MODE=\n")
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node, backup_type=None),
|
||||
six.b('ERROR: invalid backup-mode ""\n')
|
||||
)
|
||||
|
||||
self.clean_pb(node)
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
# TODO: keep data generations
|
||||
|
||||
# invalid value in pg_probackup.conf
|
||||
with open(path.join(self.backup_dir(node), "pg_probackup.conf"), "a") as conf:
|
||||
conf.write("SMOOTH_CHECKPOINT=FOO\n")
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node),
|
||||
six.b("ERROR: option -C, --smooth-checkpoint should be a boolean: 'FOO'\n")
|
||||
)
|
||||
|
||||
self.clean_pb(node)
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
# invalid option in pg_probackup.conf
|
||||
with open(path.join(self.backup_dir(node), "pg_probackup.conf"), "a") as conf:
|
||||
conf.write("TIMELINEID=1\n")
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node),
|
||||
six.b('ERROR: invalid option "TIMELINEID"\n')
|
||||
)
|
||||
|
||||
self.clean_pb(node)
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
node.stop()
|
109
tests/pb_lib.py
Normal file
109
tests/pb_lib.py
Normal file
@ -0,0 +1,109 @@
|
||||
import os
|
||||
from os import path
|
||||
import subprocess
|
||||
import shutil
|
||||
import six
|
||||
from testgres import get_new_node
|
||||
|
||||
|
||||
def dir_files(base_dir):
|
||||
out_list = []
|
||||
for dir_name, subdir_list, file_list in os.walk(base_dir):
|
||||
if dir_name != base_dir:
|
||||
out_list.append(path.relpath(dir_name, base_dir))
|
||||
for fname in file_list:
|
||||
out_list.append(path.relpath(path.join(dir_name, fname), base_dir))
|
||||
out_list.sort()
|
||||
return out_list
|
||||
|
||||
|
||||
class ProbackupTest(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProbackupTest, self).__init__(*args, **kwargs)
|
||||
self.dir_path = path.dirname(os.path.realpath(__file__))
|
||||
try:
|
||||
os.makedirs(path.join(self.dir_path, "tmp_dirs"))
|
||||
except:
|
||||
pass
|
||||
self.probackup_path = os.path.abspath(path.join(
|
||||
self.dir_path,
|
||||
"../pg_probackup"
|
||||
))
|
||||
|
||||
def arcwal_dir(self, node):
|
||||
return "%s/backup/wal" % node.base_dir
|
||||
|
||||
def backup_dir(self, node):
|
||||
return os.path.abspath("%s/backup" % node.base_dir)
|
||||
|
||||
def make_bnode(self, name, base_dir=None):
|
||||
node = get_new_node('test', base_dir=path.join(self.dir_path, base_dir))
|
||||
try:
|
||||
node.cleanup()
|
||||
except:
|
||||
pass
|
||||
shutil.rmtree(self.backup_dir(node), ignore_errors=True)
|
||||
node.init()
|
||||
|
||||
node.append_conf("postgresql.conf", "wal_level = hot_standby")
|
||||
node.append_conf("postgresql.conf", "archive_mode = on")
|
||||
node.append_conf(
|
||||
"postgresql.conf",
|
||||
"""archive_command = 'cp "%%p" "%s/%%f"'""" % os.path.abspath(self.arcwal_dir(node))
|
||||
)
|
||||
return node
|
||||
|
||||
def run_pb(self, command):
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
[self.probackup_path] + command,
|
||||
stderr=subprocess.STDOUT
|
||||
)
|
||||
except subprocess.CalledProcessError as err:
|
||||
return err.output
|
||||
|
||||
def init_pb(self, node):
|
||||
return self.run_pb([
|
||||
"init",
|
||||
"-B", self.backup_dir(node),
|
||||
"-D", node.data_dir
|
||||
])
|
||||
|
||||
def clean_pb(self, node):
|
||||
shutil.rmtree(self.backup_dir(node), ignore_errors=True)
|
||||
|
||||
def backup_pb(self, node, backup_type="full", options=[]):
|
||||
cmd_list = [
|
||||
"backup",
|
||||
"-D", node.data_dir,
|
||||
"-B", self.backup_dir(node),
|
||||
"-p", "%i" % node.port,
|
||||
"-d", "postgres"
|
||||
]
|
||||
if backup_type:
|
||||
cmd_list += ["-b", backup_type]
|
||||
|
||||
# print(cmd_list)
|
||||
return self.run_pb(cmd_list + options)
|
||||
|
||||
def show_pb(self, node, id=None, options=[]):
|
||||
cmd_list = [
|
||||
"-B", self.backup_dir(node),
|
||||
"show",
|
||||
]
|
||||
if id:
|
||||
cmd_list += [id]
|
||||
|
||||
# print(cmd_list)
|
||||
return self.run_pb(options + cmd_list)
|
||||
|
||||
def validate_pb(self, node, id=None, options=[]):
|
||||
cmd_list = [
|
||||
"-B", self.backup_dir(node),
|
||||
"validate",
|
||||
]
|
||||
if id:
|
||||
cmd_list += [id]
|
||||
|
||||
# print(cmd_list)
|
||||
return self.run_pb(options + cmd_list)
|
49
tests/show_test.py
Normal file
49
tests/show_test.py
Normal file
@ -0,0 +1,49 @@
|
||||
import unittest
|
||||
import os
|
||||
from os import path
|
||||
import six
|
||||
from .pb_lib import ProbackupTest
|
||||
from testgres import stop_all
|
||||
|
||||
|
||||
class OptionTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OptionTest, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
stop_all()
|
||||
|
||||
def test_ok_1(self):
|
||||
"""Status DONE and OK"""
|
||||
node = self.make_bnode('done_ok', base_dir="tmp_dirs/show/ok_1")
|
||||
node.start()
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node, options=["--quiet"]),
|
||||
six.b("")
|
||||
)
|
||||
self.assertIn(six.b("OK"), self.show_pb(node))
|
||||
|
||||
node.stop()
|
||||
|
||||
def test_corrupt_2(self):
|
||||
"""Status DONE and OK"""
|
||||
node = self.make_bnode('corrupt', base_dir="tmp_dirs/show/corrupt_2")
|
||||
node.start()
|
||||
self.assertEqual(self.init_pb(node), six.b(""))
|
||||
|
||||
self.assertEqual(
|
||||
self.backup_pb(node, options=["--quiet"]),
|
||||
six.b("")
|
||||
)
|
||||
|
||||
id_backup = self.show_pb(node).splitlines()[3].split()[0]
|
||||
os.remove(path.join(self.backup_dir(node), "backups", id_backup.decode("utf-8"), "database", "postgresql.conf"))
|
||||
|
||||
self.validate_pb(node, id_backup)
|
||||
self.assertIn(six.b("CORRUPT"), self.show_pb(node))
|
||||
|
||||
node.stop()
|
Loading…
x
Reference in New Issue
Block a user