# -*- coding: utf-8 -*-
# © Copyright EnterpriseDB UK Limited 2011-2025
#
# This file is part of Barman.
#
# Barman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Barman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Barman.  If not, see <http://www.gnu.org/licenses/>.

import itertools

import pytest
from mock import mock

import barman.exceptions
from barman import xlog
from barman.compression import CompressionManager
from barman.encryption import EncryptionManager
from barman.exceptions import CommandException, WalArchiveContentError
from barman.infofile import WalFileInfo


# noinspection PyMethodMayBeStatic
class Test(object):
    def test_encode_segment_name(self):
        assert xlog.encode_segment_name(0, 0, 0) == "000000000000000000000000"
        assert xlog.encode_segment_name(1, 1, 1) == "000000010000000100000001"
        assert xlog.encode_segment_name(10, 10, 10) == "0000000A0000000A0000000A"
        assert xlog.encode_segment_name(17, 17, 17) == "000000110000001100000011"
        assert xlog.encode_segment_name(0, 2, 1) == "000000000000000200000001"
        assert xlog.encode_segment_name(1, 0, 2) == "000000010000000000000002"
        assert xlog.encode_segment_name(2, 1, 0) == "000000020000000100000000"

    def test_decode_segment_name(self):
        assert xlog.decode_segment_name("000000000000000000000000") == [0, 0, 0]
        assert xlog.decode_segment_name("000000010000000100000001") == [1, 1, 1]
        assert xlog.decode_segment_name("0000000A0000000A0000000A") == [10, 10, 10]
        assert xlog.decode_segment_name("000000110000001100000011") == [17, 17, 17]
        assert xlog.decode_segment_name("000000000000000200000001") == [0, 2, 1]
        assert xlog.decode_segment_name("000000010000000000000002") == [1, 0, 2]
        assert xlog.decode_segment_name("000000020000000100000000") == [2, 1, 0]
        assert xlog.decode_segment_name("00000001000000000000000A.00000020.backup") == [
            1,
            0,
            10,
        ]
        assert xlog.decode_segment_name("00000001.history") == [1, None, None]
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.decode_segment_name("00000000000000000000000")
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.decode_segment_name("0000000000000000000000000")
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.decode_segment_name("000000000000X00000000000")

    def test_decode_hash_dir(self):
        """
        Verify the decoding of the prefix under which WALs are stored.
        """
        assert xlog.decode_hash_dir("0000000000000000") == [0, 0]
        assert xlog.decode_hash_dir("0000000100000001") == [1, 1]
        assert xlog.decode_hash_dir("0000000A0000000A") == [10, 10]
        assert xlog.decode_hash_dir("0000001100000011") == [17, 17]
        assert xlog.decode_hash_dir("0000000000000002") == [0, 2]
        assert xlog.decode_hash_dir("0000000100000000") == [1, 0]
        assert xlog.decode_hash_dir("0000000200000001") == [2, 1]
        with pytest.raises(barman.exceptions.BadXlogPrefix):
            xlog.decode_hash_dir("000000000000000")
        with pytest.raises(barman.exceptions.BadXlogPrefix):
            xlog.decode_hash_dir("000000000000000000")
        with pytest.raises(barman.exceptions.BadXlogPrefix):
            xlog.decode_hash_dir("000000000000X000")

    def test_generate_segment_names_xlog_file_size_known(self):
        assert tuple(
            xlog.generate_segment_names(
                "0000000100000001000000FD",
                "000000010000000200000002",
                90200,
                xlog.DEFAULT_XLOG_SEG_SIZE,
            )
        ) == (
            "0000000100000001000000FD",
            "0000000100000001000000FE",
            "000000010000000200000000",
            "000000010000000200000001",
            "000000010000000200000002",
        )
        assert tuple(
            xlog.generate_segment_names(
                "0000000100000001000000FD",
                "0000000100000001000000FF",
                90200,
                xlog.DEFAULT_XLOG_SEG_SIZE,
            )
        ) == ("0000000100000001000000FD", "0000000100000001000000FE")

        assert tuple(
            xlog.generate_segment_names(
                "0000000100000001000000FD",
                "000000010000000200000002",
                90300,
                xlog.DEFAULT_XLOG_SEG_SIZE,
            )
        ) == (
            "0000000100000001000000FD",
            "0000000100000001000000FE",
            "0000000100000001000000FF",
            "000000010000000200000000",
            "000000010000000200000001",
            "000000010000000200000002",
        )

        assert tuple(
            xlog.generate_segment_names(
                "0000000100000001000000FD",
                "0000000100000001000000FF",
                90300,
                xlog.DEFAULT_XLOG_SEG_SIZE,
            )
        ) == (
            "0000000100000001000000FD",
            "0000000100000001000000FE",
            "0000000100000001000000FF",
        )

        # Test the behaviour of generate_segment_names at log boundaries
        # for recent versions
        assert tuple(
            itertools.islice(
                xlog.generate_segment_names(
                    "0000000300000004000000FD",
                    xlog_segment_size=xlog.DEFAULT_XLOG_SEG_SIZE,
                ),
                6,
            )
        ) == (
            "0000000300000004000000FD",
            "0000000300000004000000FE",
            "0000000300000004000000FF",
            "000000030000000500000000",
            "000000030000000500000001",
            "000000030000000500000002",
        )

        # Test the behaviour of generate_segment_names at log boundaries
        # for versions < 9.3
        assert tuple(
            itertools.islice(
                xlog.generate_segment_names(
                    "0000000300000004000000FD",
                    version=90201,
                    xlog_segment_size=xlog.DEFAULT_XLOG_SEG_SIZE,
                ),
                6,
            )
        ) == (
            "0000000300000004000000FD",
            "0000000300000004000000FE",
            "000000030000000500000000",
            "000000030000000500000001",
            "000000030000000500000002",
            "000000030000000500000003",
        )

        # Test the number of items produced between two segments
        assert (
            sum(
                1
                for _ in xlog.generate_segment_names(
                    "000000040000000500000067",
                    "000000040000000700000067",
                    xlog_segment_size=xlog.DEFAULT_XLOG_SEG_SIZE,
                )
            )
            == 513
        )

        # The number of items produced between the same two segments is lower
        # with version < 9.3
        assert (
            sum(
                1
                for _ in xlog.generate_segment_names(
                    "000000040000000500000067",
                    "000000040000000700000067",
                    version=90201,
                    xlog_segment_size=xlog.DEFAULT_XLOG_SEG_SIZE,
                )
            )
            == 511
        )

    def test_generate_segment_names_xlog_file_size_unknown(self):
        assert tuple(
            xlog.generate_segment_names(
                "0000000100000001000000FD", "000000010000000100000102", 90200
            )
        ) == (
            "0000000100000001000000FD",
            "0000000100000001000000FE",
            "0000000100000001000000FF",
            "000000010000000100000100",
            "000000010000000100000101",
            "000000010000000100000102",
        )

        assert tuple(
            xlog.generate_segment_names(
                "00000001000000010007FFFE", "000000010000000200000002", 90300
            )
        ) == (
            "00000001000000010007FFFE",
            "00000001000000010007FFFF",
            "000000010000000200000000",
            "000000010000000200000001",
            "000000010000000200000002",
        )

        # The last segment of a file is skipped in
        # PostgreSQL < 9.3
        assert tuple(
            xlog.generate_segment_names(
                "00000001000000010007FFFE", "000000010000000200000002", 90200
            )
        ) == (
            "00000001000000010007FFFE",
            "000000010000000200000000",
            "000000010000000200000001",
            "000000010000000200000002",
        )

    def test_hash_dir(self):
        assert xlog.hash_dir("000000000000000200000001") == "0000000000000002"
        assert xlog.hash_dir("000000010000000000000002") == "0000000100000000"
        assert xlog.hash_dir("test/000000020000000100000000") == "0000000200000001"
        assert xlog.hash_dir("00000001.history") == ""
        assert xlog.hash_dir("00000002.history") == ""
        assert (
            xlog.hash_dir("00000001000000000000000A.00000020.backup")
            == "0000000100000000"
        )
        assert (
            xlog.hash_dir("00000002000000050000000A.00000020.backup")
            == "0000000200000005"
        )
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.hash_dir("00000000000000000000000")
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.hash_dir("0000000000000000000000000")
        with pytest.raises(barman.exceptions.BadXlogSegmentName):
            xlog.hash_dir("000000000000X00000000000")

    def test_is_any_xlog_file(self):
        assert xlog.is_any_xlog_file("000000000000000200000001")
        assert xlog.is_any_xlog_file("test1/000000000000000200000001")
        assert xlog.is_any_xlog_file("00000001000000000000000A.00000020.backup")
        assert xlog.is_any_xlog_file("test2/00000001000000000000000A.00000020.backup")
        assert xlog.is_any_xlog_file("00000001000000000000000A.partial")
        assert xlog.is_any_xlog_file("test2/00000001000000000000000A.partial")
        assert xlog.is_any_xlog_file("00000002.history")
        assert xlog.is_any_xlog_file("test3/00000002.history")
        assert not xlog.is_any_xlog_file("00000000000000000000000")
        assert not xlog.is_any_xlog_file("0000000000000000000000000")
        assert not xlog.is_any_xlog_file("000000000000X00000000000")
        assert not xlog.is_any_xlog_file("00000001000000000000000A.backup")
        assert not xlog.is_any_xlog_file(
            "test.00000001000000000000000A.00000020.backup"
        )
        assert not xlog.is_any_xlog_file(
            "test.00000001000000000000000A.00000020.partial"
        )
        assert not xlog.is_any_xlog_file("00000001000000000000000A.history")

    def test_history_file(self):
        assert not xlog.is_history_file("000000000000000200000001")
        assert not xlog.is_history_file("00000001000000000000000A.00000020.backup")
        assert xlog.is_history_file("00000002.history")
        assert xlog.is_history_file("test/00000002.history")
        assert not xlog.is_history_file("00000000000000000000000")
        assert not xlog.is_history_file("0000000000000000000000000")
        assert not xlog.is_history_file("000000000000X00000000000")
        assert not xlog.is_history_file("00000001000000000000000A.backup")
        assert not xlog.is_any_xlog_file(
            "test.00000001000000000000000A.00000020.backup"
        )
        assert not xlog.is_history_file("00000001000000000000000A.history")
        assert not xlog.is_history_file("00000001000000000000000A.partial")
        assert not xlog.is_history_file("00000001.partial")

    def test_backup_file(self):
        assert not xlog.is_backup_file("000000000000000200000001")
        assert xlog.is_backup_file("00000001000000000000000A.00000020.backup")
        assert xlog.is_backup_file("test/00000001000000000000000A.00000020.backup")
        assert not xlog.is_backup_file("00000002.history")
        assert not xlog.is_backup_file("00000000000000000000000")
        assert not xlog.is_backup_file("0000000000000000000000000")
        assert not xlog.is_backup_file("000000000000X00000000000")
        assert not xlog.is_backup_file("00000001000000000000000A.backup")
        assert not xlog.is_any_xlog_file(
            "test.00000001000000000000000A.00000020.backup"
        )
        assert not xlog.is_backup_file("00000001000000000000000A.history")
        assert not xlog.is_backup_file("00000001000000000000000A.partial")
        assert not xlog.is_backup_file("00000001000000000000000A.00000020.partial")

    def test_partial_file(self):
        assert not xlog.is_partial_file("000000000000000200000001")
        assert xlog.is_partial_file("00000001000000000000000A.partial")
        assert xlog.is_partial_file("test/00000001000000000000000A.partial")
        assert not xlog.is_partial_file("00000002.history")
        assert not xlog.is_partial_file("00000000000000000000000.partial")
        assert not xlog.is_partial_file("0000000000000000000000000.partial")
        assert not xlog.is_partial_file("000000000000X00000000000.partial")
        assert not xlog.is_partial_file("00000001000000000000000A.00000020.partial")
        assert not xlog.is_any_xlog_file("test.00000001000000000000000A.partial")
        assert not xlog.is_partial_file("00000001.partial")

    def test_is_wal_file(self):
        assert xlog.is_wal_file("000000000000000200000001")
        assert xlog.is_wal_file("test/000000000000000200000001")
        assert not xlog.is_wal_file("00000001000000000000000A.00000020.backup")
        assert not xlog.is_wal_file("00000002.history")
        assert not xlog.is_wal_file("00000000000000000000000")
        assert not xlog.is_wal_file("0000000000000000000000000")
        assert not xlog.is_wal_file("000000000000X00000000000")
        assert not xlog.is_wal_file("00000001000000000000000A.backup")
        assert not xlog.is_any_xlog_file(
            "test.00000001000000000000000A.00000020.backup"
        )
        assert not xlog.is_wal_file("00000001000000000000000A.history")
        assert not xlog.is_wal_file("00000001000000000000000A.partial")

    def test_encode_history_filename(self):
        assert xlog.encode_history_file_name(1) == "00000001.history"
        assert xlog.encode_history_file_name(10) == "0000000A.history"
        assert xlog.encode_history_file_name(33) == "00000021.history"
        assert xlog.encode_history_file_name(328) == "00000148.history"

    def test_decode_history_file(self, tmpdir):
        config_mock = mock.Mock()
        config_mock.compression = "gzip"
        comp_manager = CompressionManager(config_mock, None)
        encryption_manager = EncryptionManager(config_mock, None)
        compressor = mock.Mock()

        # Regular history file
        p = tmpdir.join("00000002.history")
        p.write('1\t2/83000168\tat restore point "myrp"\n')
        wal_info = WalFileInfo.from_file(
            p.strpath, comp_manager, encryption_manager=encryption_manager
        )
        result = xlog.HistoryFileData(
            tli=2,
            parent_tli=1,
            reason='at restore point "myrp"',
            switchpoint=0x283000168,
        )
        assert xlog.decode_history_file(wal_info, compressor) == [result]
        assert len(compressor.mock_calls) == 0

        # Comments must be skipped
        p = tmpdir.join("00000003.history")
        p.write('# Comment\n1\t2/83000168\tat restore point "testcomment"\n')
        wal_info = WalFileInfo.from_file(
            p.strpath, comp_manager, encryption_manager=encryption_manager
        )
        result = xlog.HistoryFileData(
            tli=3,
            parent_tli=1,
            reason='at restore point "testcomment"',
            switchpoint=0x283000168,
        )
        assert xlog.decode_history_file(wal_info, compressor) == [result]
        assert len(compressor.mock_calls) == 0

        # History file with comments and empty lines
        p = tmpdir.join("00000004.history")
        p.write('# Comment\n\n1\t2/83000168\ttesting "testemptyline"\n')
        wal_info = WalFileInfo.from_file(
            p.strpath, comp_manager, encryption_manager=encryption_manager
        )
        result = xlog.HistoryFileData(
            tli=4,
            parent_tli=1,
            reason='testing "testemptyline"',
            switchpoint=0x283000168,
        )
        assert xlog.decode_history_file(wal_info, compressor) == [result]
        assert len(compressor.mock_calls) == 0

        # Test compression handling Fix for bug #66 on github
        # check custom compression method creation
        u = tmpdir.join("00000005.uncompressed")
        p = tmpdir.join("00000005.history")
        u.write('1\t2/83000168\tat restore point "myrp"\n')
        result = xlog.HistoryFileData(
            tli=5,
            parent_tli=1,
            reason='at restore point "myrp"',
            switchpoint=0x283000168,
        )
        comp_manager.get_compressor("gzip").compress(u.strpath, p.strpath)
        wal_info = WalFileInfo.from_file(
            p.strpath, comp_manager, encryption_manager=encryption_manager
        )
        assert xlog.decode_history_file(wal_info, comp_manager) == [result]

        with pytest.raises(barman.exceptions.BadHistoryFileContents):
            # Empty file
            p.write("")
            assert xlog.decode_history_file(wal_info, compressor)
            assert len(compressor.mock_calls) == 0

        with pytest.raises(barman.exceptions.BadHistoryFileContents):
            # Missing field
            p.write("1\t2/83000168")
            assert xlog.decode_history_file(wal_info, compressor)
            assert len(compressor.mock_calls) == 0

        with pytest.raises(barman.exceptions.BadHistoryFileContents):
            # Unattended field
            p.write('1\t2/83000168\tat restore point "myrp"\ttest')
            assert xlog.decode_history_file(wal_info, compressor)
            assert len(compressor.mock_calls) == 0

    def test_parse_lsn(self):
        assert xlog.parse_lsn("2/8300168") == ((2 << 32) + 0x8300168)
        assert xlog.parse_lsn("FFFFFFFF/FFFFFFFF") == ((0xFFFFFFFF << 32) + 0xFFFFFFFF)
        assert xlog.parse_lsn("0/0") == 0
        with pytest.raises(ValueError):
            xlog.parse_lsn("DEADBEEF")

    def test_format_lsn(self):
        assert xlog.format_lsn(0x123456789ABCDEF) == "1234567/89ABCDEF"

    def test_diff_lsn(self):
        assert xlog.diff_lsn("2/8300168", "1/8300168") == 1 << 32
        assert xlog.diff_lsn("2/8300168", "2/8100168") == 0x200000
        assert xlog.diff_lsn(None, "2/8100168") is None
        assert xlog.diff_lsn("2/8300168", None) is None

    @pytest.mark.parametrize(
        "size, name, offset, lsn",
        [
            [24, "000000030000000A00000012", 0x345678, "A/12345678"],
            [28, "000000030000000A00000001", 0x2345678, "A/12345678"],
            [20, "000000030000000A00000123", 0x45678, "A/12345678"],
            [26, "000000030000011300000022", 0xB5ADC0, "113/88B5ADC0"],
            [28, "000000030000005600000003", 0x80537A8, "56/380537A8"],
        ],
    )
    def test_location_to_xlogfile_name_offset(self, size, name, offset, lsn):
        result = xlog.location_to_xlogfile_name_offset(lsn, 3, 1 << size)
        assert result == {
            "file_name": name,
            "file_offset": offset,
        }

    @pytest.mark.parametrize(
        "size, name, offset, lsn",
        [
            [24, "000000030000000A00000012", 0x345678, "A/12345678"],
            [28, "000000030000000A00000001", 0x2345678, "A/12345678"],
            [20, "000000030000000A00000123", 0x45678, "A/12345678"],
            [26, "000000030000011300000022", 0xB5ADC0, "113/88B5ADC0"],
            [28, "000000030000005600000003", 0x80537A8, "56/380537A8"],
        ],
    )
    def test_location_from_xlogfile_name_offset(self, size, name, offset, lsn):
        assert xlog.location_from_xlogfile_name_offset(name, offset, 1 << size) == lsn

    @pytest.mark.parametrize(
        "segments, size",
        [
            # There are 1023 segments with 4 MiB XLOG file size
            [1023, 1 << 22],
            # There are 511 segments with 8 MiB XLOG file size
            [511, 1 << 23],
            # There are 255 segments with 16 MiB (default) XLOG file size
            [255, 1 << 24],
            # There are 127 segments with 32 MiB XLOG file size
            [127, 1 << 25],
            # There are 63 segments with 64 MiB XLOG file size
            [63, 1 << 26],
            # There are 31 segments with 128 MiB XLOG file size
            [31, 1 << 27],
            # There are 15 segments with 256 MiB XLOG file size
            [15, 1 << 28],
            # There are 7 segments with 512 MiB XLOG file size
            [7, 1 << 29],
            # There are 3 segments with 1 GiB XLOG file size
            [3, 1 << 30],
        ],
    )
    def test_xlog_segment_in_file(self, segments, size):
        assert segments == xlog.xlog_segments_per_file(size)

    @pytest.mark.parametrize(
        "mask, size",
        [
            # There are 255 segments with default XLOG file size
            [0xFF000000, 1 << 24],
            # There are 63 segments with 64 MiB XLOG file size
            [0xFC000000, 1 << 26],
            # There are 1023 segments with 4 MiB XLOG file size
            [0xFFC00000, 1 << 22],
        ],
    )
    def test_xlog_segment_mask(self, mask, size):
        assert mask == xlog.xlog_segment_mask(size)


class TestCheckArchiveUsable(object):
    EXPECTED_EMPTY_MESSAGE = "Expected empty archive"
    EXPECTED_ARCHIVE_CONTENT_ERROR = (
        "Found %s file%s in WAL archive equal to or newer than timeline %s"
    )

    def test_passes_if_no_wals(self):
        xlog.check_archive_usable([])

    def test_fails_if_wal_exists(self):
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(["000000010000000000000001"])
        assert self.EXPECTED_EMPTY_MESSAGE == str(exc.value)

    def test_fails_if_history_exists(self):
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(["00000002.history"])
        assert self.EXPECTED_EMPTY_MESSAGE == str(exc.value)

    def test_fails_if_backup_wal_exists(self):
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(["000000010000000000000001.00000028.backup"])
        assert self.EXPECTED_EMPTY_MESSAGE == str(exc.value)

    @pytest.mark.parametrize("bad_timeline", [-1, 0, "a string"])
    def test_error_if_timeline_is_malformed(self, bad_timeline):
        with pytest.raises(CommandException) as exc:
            xlog.check_archive_usable(
                [],
                timeline=bad_timeline,
            )
        assert (
            "Cannot check WAL archive with malformed timeline %s" % bad_timeline
            == str(exc.value)
        )

    def test_passes_if_timeline_with_no_wals(self):
        xlog.check_archive_usable([], timeline=1)

    @pytest.mark.parametrize(
        "wals,timeline",
        [
            (["00000002.history"], 3),
            (["00000002.history", "00000003.history"], 4),
            (["00000003.history", "00000002.history"], 4),
        ],
    )
    def test_passes_if_timeline_gt_history_file(self, wals, timeline):
        xlog.check_archive_usable(
            wals,
            timeline=timeline,
        )

    @pytest.mark.parametrize(
        "wals,timeline,num_expected_newer_files",
        [
            (["00000002.history"], 1, 1),
            (["00000002.history", "00000003.history"], 2, 2),
            (["00000002.history", "00000003.history"], 3, 1),
            (["00000003.history", "00000002.history"], 2, 2),
            (["00000003.history", "00000002.history"], 3, 1),
        ],
    )
    def test_fails_if_timeline_lte_history_file(
        self, wals, timeline, num_expected_newer_files
    ):
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(
                wals,
                timeline=timeline,
            )
        assert self.EXPECTED_ARCHIVE_CONTENT_ERROR % (
            num_expected_newer_files,
            num_expected_newer_files > 1 and "s" or "",
            timeline,
        ) == str(exc.value)

    @pytest.mark.parametrize(
        "wals,timeline",
        [
            (["000000010000000000000001"], 2),
            (["000000020000000000000001"], 3),
            (
                [
                    "000000010000000000000001",
                    "000000010000000000000002",
                    "000000010000000000000003",
                ],
                2,
            ),
            (
                [
                    "000000010000000000000001",
                    "000000010000000000000002",
                    "000000020000000000000001",
                ],
                3,
            ),
        ],
    )
    def test_passes_if_timeline_gt_wal_file_timeline(self, wals, timeline):
        xlog.check_archive_usable(
            wals,
            timeline=timeline,
        )

    @pytest.mark.parametrize(
        "wals,timeline,num_expected_illegal_files",
        [
            (["000000020000000000000001"], 1, 1),
            (["000000030000000000000001"], 2, 1),
            (
                [
                    "000000020000000000000001",
                    "000000020000000000000002",
                    "000000020000000000000003",
                ],
                1,
                3,
            ),
            (
                [
                    "000000020000000000000001",
                    "000000020000000000000002",
                    "000000030000000000000001",
                ],
                2,
                3,
            ),
        ],
    )
    def test_fails_if_timeline_lte_wal_file_timeline(
        self, wals, timeline, num_expected_illegal_files
    ):
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(
                wals,
                timeline=timeline,
            )
        assert self.EXPECTED_ARCHIVE_CONTENT_ERROR % (
            num_expected_illegal_files,
            num_expected_illegal_files > 1 and "s" or "",
            timeline,
        ) == str(exc.value)

    @pytest.mark.parametrize(
        "wals",
        [
            ["00000002.history", "0"],
            ["000000010000000100000001", 0],
            ["000000010000000100000001", "00000001000000010000000G"],
            ["000000010000000100000001", "000000010000000100000001.gz"],
        ],
    )
    def test_fails_if_existing_wals_malformed(self, wals):
        timeline = 2
        with pytest.raises(WalArchiveContentError) as exc:
            xlog.check_archive_usable(
                wals,
                timeline=timeline,
            )
        assert "Unexpected file %s found in WAL archive" % wals[1] == str(exc.value)
