def test_command_line_postgres_overrides(self): xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-c", "unix_socket_directory=socket_dir", "-D", "data_directory"], {}) self.assertEqual(xpg.socket_directory, "socket_dir") xpg.parse_command_line(["xpostgres", "-c", "log_directory=log_directory"], {}) self.assertEqual(xpg.log_directory, "log_directory")
def test_toggle_wal_archive_logging(self): """ L{XPostgres.toggle_wal_archive_logging} will update two postgres configuration files as needed to enable or disable WAL archiving. """ hba_config_default = """\ # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. #local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ postgres_config_default = """# ----------------------------- #wal_level = minimal # minimal, archive, or hot_standby # (change requires restart) #archive_mode = off # allows archiving to be done # (change requires restart) #archive_command = '' # command to use to archive a logfile segment # placeholders: %p = path of file to archive # %f = file name only # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' #archive_timeout = 0 # force a logfile segment switch after this # number of seconds; 0 disables #max_wal_senders = 0 # max number of walsender processes # (change requires restart) #some random stuff that is not updated """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) postgres_conf_path = os.path.join(data_dir, "postgresql.conf") config_file = open(postgres_conf_path, "wb") config_file.write(postgres_config_default) config_file.close() pg_hba_file = open(os.path.join(data_dir, "pg_hba.conf"), "wb") pg_hba_file.write(hba_config_default) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.toggle_wal_archive_logging(True) config_file = open(postgres_conf_path, "rb") self.assertEqual(True, xpg.wal_archiving_is_enabled(config_file.readlines())) config_file.close() xpg.toggle_wal_archive_logging(False) config_file = open(postgres_conf_path, "rb") self.assertEqual(False, xpg.wal_archiving_is_enabled(config_file.readlines())) config_file.close()
def test_parse_command_line(self): xpg = XPostgres(DummyProcessReactor()) plname = self.makePlist() xpg.parse_command_line(["xpostgres", "-a", plname, "-D", "data_directory", "-k", "socket_directory"], {}) self.assertEqual(xpg.plist_path, plname) self.assertEqual(xpg.data_directory, "data_directory") self.assertEqual(xpg.socket_directory, "socket_directory") self.assertEqual(xpg.archive_log_directory, ARCHIVE_LOG_DIRECTORY_NAME)
def test_command_line_excludes_plist_option(self): """ The C{-a} option is specific to C{xpostgres} and won't be passed on to postgres. """ xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-a", self.makePlist(), "-D", "data_directory", "-k", "socket_directory"], {}) self.assertEqual(xpg.postgres_argv, ["-D", "data_directory", "-k", "socket_directory"])
def test_parse_command_line(self): xpg = XPostgres(DummyProcessReactor()) plname = self.makePlist() xpg.parse_command_line([ "xpostgres", "-a", plname, "-D", "data_directory", "-k", "socket_directory" ], {}) self.assertEqual(xpg.plist_path, plname) self.assertEqual(xpg.data_directory, "data_directory") self.assertEqual(xpg.socket_directory, "socket_directory") self.assertEqual(xpg.archive_log_directory, ARCHIVE_LOG_DIRECTORY_NAME)
def test_command_line_excludes_plist_option(self): """ The C{-a} option is specific to C{xpostgres} and won't be passed on to postgres. """ xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-a", self.makePlist(), "-D", "data_directory", "-k", "socket_directory" ], {}) self.assertEqual(xpg.postgres_argv, ["-D", "data_directory", "-k", "socket_directory"])
def test_enable_connection_restriction(self): """ L{XPostgres.enable_connection_restriction} will configure pg_hba.conf so that only local replication connections are accepted. All others should be denied. """ hba_config_default = """\ # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) pg_hba_file_path = os.path.join(data_dir, "pg_hba.conf") pg_hba_file = open(pg_hba_file_path, "wb") pg_hba_file.write(hba_config_default) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.enable_connection_restriction() pg_hba_file = open(pg_hba_file_path, "rb") connections_are_restricted = True for line in pg_hba_file.readlines(): matchobj = re.match(r"^#", line) if (matchobj): continue matchobj = re.match(r"^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(\S*)$", line) if (matchobj): (type, database, user) = matchobj.group(1, 2, 3) if matchobj.group(5): (address, method) = matchobj.group(4, 5) # else: # method = matchobj.group(4) if database != "replication": connections_are_restricted = False break pg_hba_file.close() self.assertEqual(True, connections_are_restricted)
def test_toggle_wal_archive_logging(self): """ L{XPostgres.toggle_wal_archive_logging} will update two postgres configuration files as needed to enable or disable WAL archiving. """ hba_config_default = """\ # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. #local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ postgres_config_default = """# ----------------------------- #wal_level = minimal # minimal, archive, or hot_standby # (change requires restart) #archive_mode = off # allows archiving to be done # (change requires restart) #archive_command = '' # command to use to archive a logfile segment # placeholders: %p = path of file to archive # %f = file name only # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' #archive_timeout = 0 # force a logfile segment switch after this # number of seconds; 0 disables #max_wal_senders = 0 # max number of walsender processes # (change requires restart) #some random stuff that is not updated """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) postgres_conf_path = os.path.join(data_dir, "postgresql.conf") config_file = open(postgres_conf_path, "wb") config_file.write(postgres_config_default) config_file.close() pg_hba_file = open(os.path.join(data_dir, "pg_hba.conf"), "wb") pg_hba_file.write(hba_config_default) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.toggle_wal_archive_logging(True) config_file = open(postgres_conf_path, "rb") self.assertEqual(True, xpg.wal_archiving_is_enabled(config_file.readlines())) config_file.close() xpg.toggle_wal_archive_logging(False) config_file = open(postgres_conf_path, "rb") self.assertEqual(False, xpg.wal_archiving_is_enabled(config_file.readlines())) config_file.close()
def test_data_directory_create(self): temp = self.mktemp() os.makedirs(temp) xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-c", "unix_socket_directory=socket_dir", "-D", temp], {} ) xpg.preflight() backup_dir = os.path.join(os.path.dirname(temp), ARCHIVE_LOG_DIRECTORY_NAME) self.assertEqual(True, os.path.isdir(backup_dir)) self.assertEqual(0o0700, os.stat(backup_dir).st_mode & 0o0777)
def test_no_restore_if_tinkle_present(self): data_dir = self.mktemp() backup_dir = self.mktemp() os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) tinkle = os.path.join(data_dir, RESTORE_ON_ABSENCE_FILE) open(tinkle, "wb").close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-c", "unix_socket_directory=socket_dir", "-D", data_dir], {} ) xpg.preflight() self.assertEqual(False, xpg.restore_before_run)
def test_arguments_plist(self, option='-a'): """ The C{-a} option extends the command line from a property list including the L{ProgramArguments} key. """ xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", option, self.makePlist(["-D", "data_directory", "-k", "socket_directory"]) ], {}) self.assertEqual(xpg.data_directory, "data_directory") self.assertEqual(xpg.socket_directory, "socket_directory") self.assertEqual(xpg.archive_log_directory, ARCHIVE_LOG_DIRECTORY_NAME) self.assertEqual(xpg.postgres_argv, ["-D", "data_directory", "-k", "socket_directory"])
def test_set_restore_if_tinkle_missing(self): data_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-c", "unix_socket_directory=socket_dir", "-D", data_dir], {} ) xpg.backup_zip_file.parent().createDirectory() xpg.backup_zip_file.touch() xpg.preflight() self.assertEqual(True, xpg.restore_before_run)
def test_start_postgres(self): """ L{XPostgres.start_postgres} will wait for the data path to exist, clean up lock and socket files, and start postgres. """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) dpr = DummyProcessReactor() xpg = XPostgres(dpr) sktd = self.mktemp() xpg.parse_command_line(['xpostgres', '-k', sktd], {'PGDATA': data_dir}) xpg.preflight() started = xpg.start_postgres() self.assertEqual(len(dpr.spawnedProcesses), 1) self.assertEquals( list(dpr.spawnedProcesses[0]._args), [WAIT4PATH, os.path.abspath(xpg.data_directory)]) # It waits for 'wait4path' to exit, then invokes PG ... dpr.spawnedProcesses[0].processEnded(0) self.assertEquals(len(dpr.spawnedProcesses), 2) self.assertEqual(dpr.spawnedProcesses[-1]._args, [POSTGRES, '-k', sktd]) self.assertEqual(dpr.spawnedProcesses[-1]._executable, POSTGRES) self.assertEqual(dpr.spawnedProcesses[-1]._environment, {"PGDATA": data_dir}) l = [] started.addCallback(l.append) dpr.advance(5) self.assertEquals(l, []) sd = FilePath(sktd) dpr.advance(2) self.assertEquals(l, []) sd.child(".s.PGSQL.5432").touch() dpr.advance(2) self.assertEquals(l, [None])
def test_arguments_plist(self, option='-a'): """ The C{-a} option extends the command line from a property list including the L{ProgramArguments} key. """ xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( [ "xpostgres", option, self.makePlist( ["-D", "data_directory", "-k", "socket_directory"] ) ], {} ) self.assertEqual(xpg.data_directory, "data_directory") self.assertEqual(xpg.socket_directory, "socket_directory") self.assertEqual(xpg.archive_log_directory, ARCHIVE_LOG_DIRECTORY_NAME) self.assertEqual(xpg.postgres_argv, ["-D", "data_directory", "-k", "socket_directory"])
def test_disable_connection_restriction(self): """ L{XPostgres.disable_connection_restriction} will configure pg_hba.conf so that any changes made by xpostgres to restrict connections are reverted. """ hba_config = """\ # "local" is for Unix domain socket connections only #local all all trust \ # UPDATED BY xpostgres # IPv4 local connections: #host all all 127.0.0.1/32 trust \ # UPDATED BY xpostgres # IPv6 local connections: #host all all ::1/128 trust \ # UPDATED BY xpostgres # Allow replication connections from localhost, by a user with the # replication privilege. local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) pg_hba_file_path = os.path.join(data_dir, "pg_hba.conf") pg_hba_file = open(pg_hba_file_path, "wb") pg_hba_file.write(hba_config) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.disable_connection_restriction() pg_hba_file = open(pg_hba_file_path, "rb") found_our_cruft = False for line in pg_hba_file.readlines(): matchobj = re.match(r".*UPDATED BY xpostgres.*", line) if (matchobj): found_our_cruft = True pg_hba_file.close() self.assertEqual(False, found_our_cruft)
def test_start_postgres_if_tinkle_missing_but_data_present(self): """ If the tinkle file is missing but the data directory is present, L{XPostgres.do_everything} will continue to start postgres. """ xpg = self.xpg_with_backup_dir(tinkle_exists=False) started = [] def record_start(): started.append(True) return succeed(None) self.reactor = DummyProcessReactor() data_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) xpg = XPostgres(self.reactor) xpg.do_everything([xpostgres.__file__, "-D", data_dir, "-k", self.mktemp()], {})
def test_start_postgres(self): """ L{XPostgres.start_postgres} will wait for the data path to exist, clean up lock and socket files, and start postgres. """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) dpr = DummyProcessReactor() xpg = XPostgres(dpr) sktd = self.mktemp() xpg.parse_command_line( ['xpostgres', '-k', sktd], {'PGDATA': data_dir}) xpg.preflight() started = xpg.start_postgres() self.assertEqual(len(dpr.spawnedProcesses), 1) self.assertEquals(list(dpr.spawnedProcesses[0]._args), [WAIT4PATH, os.path.abspath(xpg.data_directory)]) # It waits for 'wait4path' to exit, then invokes PG ... dpr.spawnedProcesses[0].processEnded(0) self.assertEquals(len(dpr.spawnedProcesses), 2) self.assertEqual(dpr.spawnedProcesses[-1]._args, [POSTGRES, '-k', sktd]) self.assertEqual(dpr.spawnedProcesses[-1]._executable, POSTGRES) self.assertEqual(dpr.spawnedProcesses[-1]._environment, {"PGDATA": data_dir}) l = [] started.addCallback(l.append) dpr.advance(5) self.assertEquals(l, []) sd = FilePath(sktd) dpr.advance(2) self.assertEquals(l, []) sd.child(".s.PGSQL.5432").touch() dpr.advance(2) self.assertEquals(l, [None])
def test_sanitize_pid_file(self): """ L{XPostgres.sanitize_pid_file} will strip unwanted shared memory addrs from the postgres lock file. """ pid_file_content = """\ 55487 /Library/Server/Calendar and Contacts/Data/Database.xpg/cluster.pg 1383604692 5432 /var/run/caldavd/ccs_postgres_3d403b3009fe0c830944d87bd751fbe9 0 131074 """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) pid_file_path = os.path.join(data_dir, "postmaster.pid") pid_file = open(pid_file_path, "wb") pid_file.write(pid_file_content) pid_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.sanitize_pid_file() pid_file = open(pid_file_path, "rb") found_shm_addr = False for line in pid_file.readlines(): matchobj = re.match(r".*131074.*", line) if (matchobj): found_shm_addr = True pid_file.close() self.assertEqual(False, found_shm_addr)
def test_start_postgres_if_tinkle_missing_but_data_present(self): """ If the tinkle file is missing but the data directory is present, L{XPostgres.do_everything} will continue to start postgres. """ xpg = self.xpg_with_backup_dir(tinkle_exists=False) started = [] def record_start(): started.append(True) return succeed(None) self.reactor = DummyProcessReactor() data_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) xpg = XPostgres(self.reactor) xpg.do_everything( [xpostgres.__file__, "-D", data_dir, "-k", self.mktemp()], {})
def xpg_with_backup_dir(self, data_exists=True, backup_exists=True, tinkle_exists=True): """ Create an L{XPostgres} object with a data directory. """ self.reactor = DummyProcessReactor() data_dir = self.mktemp() socket_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) if data_exists: os.mkdir(data_dir, 0o700) if backup_exists: os.mkdir(backup_dir, 0o700) self.tinkle = os.path.join(data_dir, RESTORE_ON_ABSENCE_FILE) if tinkle_exists: open(self.tinkle, "wb").close() xpg = XPostgres(self.reactor) xpg.parse_command_line([xpostgres.__file__, "-k", socket_dir, "-D", data_dir], {}) return xpg
def xpg_with_backup_dir(self, data_exists=True, backup_exists=True, tinkle_exists=True): """ Create an L{XPostgres} object with a data directory. """ self.reactor = DummyProcessReactor() data_dir = self.mktemp() socket_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) if data_exists: os.mkdir(data_dir, 0o700) if backup_exists: os.mkdir(backup_dir, 0o700) self.tinkle = os.path.join(data_dir, RESTORE_ON_ABSENCE_FILE) if tinkle_exists: open(self.tinkle, "wb").close() xpg = XPostgres(self.reactor) xpg.parse_command_line( [xpostgres.__file__, "-k", socket_dir, "-D", data_dir], {}) return xpg
def test_command_line_postgres_overrides(self): xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-c", "unix_socket_directories=socket_dir", "-D", "data_directory" ], {}) self.assertEqual(xpg.socket_directory, "socket_dir") xpg.parse_command_line( ["xpostgres", "-c", "log_directory=log_directory"], {}) self.assertEqual(xpg.log_directory, "log_directory")
def test_data_directory_create(self): temp = self.mktemp() os.makedirs(temp) xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-c", "unix_socket_directories=socket_dir", "-D", temp ], {}) xpg.preflight() backup_dir = os.path.join(os.path.dirname(temp), ARCHIVE_LOG_DIRECTORY_NAME) self.assertEqual(True, os.path.isdir(backup_dir)) self.assertEqual(0o0700, os.stat(backup_dir).st_mode & 0o0777)
def test_enable_connection_restriction(self): """ L{XPostgres.enable_connection_restriction} will configure pg_hba.conf so that only local replication connections are accepted. All others should be denied. """ hba_config_default = """\ # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) pg_hba_file_path = os.path.join(data_dir, "pg_hba.conf") pg_hba_file = open(pg_hba_file_path, "wb") pg_hba_file.write(hba_config_default) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.enable_connection_restriction() pg_hba_file = open(pg_hba_file_path, "rb") connections_are_restricted = True for line in pg_hba_file.readlines(): matchobj = re.match(r"^#", line) if (matchobj): continue matchobj = re.match(r"^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(\S*)$", line) if (matchobj): (type, database, user) = matchobj.group(1, 2, 3) if matchobj.group(5): (address, method) = matchobj.group(4, 5) # else: # method = matchobj.group(4) if database != "replication": connections_are_restricted = False break pg_hba_file.close() self.assertEqual(True, connections_are_restricted)
def test_no_restore_if_tinkle_present(self): data_dir = self.mktemp() backup_dir = self.mktemp() os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) tinkle = os.path.join(data_dir, RESTORE_ON_ABSENCE_FILE) open(tinkle, "wb").close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-c", "unix_socket_directories=socket_dir", "-D", data_dir ], {}) xpg.preflight() self.assertEqual(False, xpg.restore_before_run)
def test_set_restore_if_tinkle_missing(self): data_dir = self.mktemp() backup_dir = os.path.join(os.path.dirname(data_dir), ARCHIVE_LOG_DIRECTORY_NAME) os.mkdir(data_dir, 0o700) os.mkdir(backup_dir, 0o700) xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line([ "xpostgres", "-c", "unix_socket_directories=socket_dir", "-D", data_dir ], {}) xpg.backup_zip_file.parent().createDirectory() xpg.backup_zip_file.touch() xpg.preflight() self.assertEqual(True, xpg.restore_before_run)
def test_disable_connection_restriction(self): """ L{XPostgres.disable_connection_restriction} will configure pg_hba.conf so that any changes made by xpostgres to restrict connections are reverted. """ hba_config = """\ # "local" is for Unix domain socket connections only #local all all trust \ # UPDATED BY xpostgres # IPv4 local connections: #host all all 127.0.0.1/32 trust \ # UPDATED BY xpostgres # IPv6 local connections: #host all all ::1/128 trust \ # UPDATED BY xpostgres # Allow replication connections from localhost, by a user with the # replication privilege. local replication _postgres trust #host replication _postgres 127.0.0.1/32 trust #host replication _postgres ::1/128 trust """ data_dir = self.mktemp() os.mkdir(data_dir, 0o700) pg_hba_file_path = os.path.join(data_dir, "pg_hba.conf") pg_hba_file = open(pg_hba_file_path, "wb") pg_hba_file.write(hba_config) pg_hba_file.close() xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line( ["xpostgres", "-D", os.path.abspath(data_dir)], {}) xpg.disable_connection_restriction() pg_hba_file = open(pg_hba_file_path, "rb") found_our_cruft = False for line in pg_hba_file.readlines(): matchobj = re.match(r".*UPDATED BY xpostgres.*", line) if (matchobj): found_our_cruft = True pg_hba_file.close() self.assertEqual(False, found_our_cruft)
def test_parse_environment(self): xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres"], dict(PGDATA="data_directory")) self.assertEqual(xpg.data_directory, "data_directory")
def test_default_input_args(self): xpg = XPostgres(DummyProcessReactor()) xpg.parse_command_line(["xpostgres", "-D", "data_directory"], {}) self.assertEqual(xpg.socket_directory, DEFAULT_SOCKET_DIR)
def test_data_directory_is_defined(self): xpg = XPostgres(DummyProcessReactor()) self.assertRaises(NoDataDirectory, xpg.parse_command_line, ["xpostgres"], {})