Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/qlever/commands/upgrade_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from __future__ import annotations

from qlever.command import QleverCommand
from qlever.containerize import Containerize
from qlever.log import log
from qlever.util import binary_exists, run_command


class UpgradeIndexCommand(QleverCommand):
"""
Class for executing the `upgrade-index` command.
"""

def __init__(self):
pass

def description(self) -> str:
return (
"Upgrade an index in the previous index format to the "
"format introduced on 2026-09-01 (only this conversion, "
"older indexes have to be rebuilt)"
)

def should_have_qleverfile(self) -> bool:
return True

def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
return {
"data": ["name"],
"index": ["index_binary"],
"runtime": ["system", "image", "index_container"],
}

def additional_arguments(self, subparser) -> None:
subparser.add_argument(
"--upgrade-index-binary",
type=str,
default=None,
help="The binary for upgrading the index (default: "
"`qlever-upgrade-index` from the directory of the "
"index binary)",
)

def execute(self, args) -> bool:
# By default, take the `qlever-upgrade-index` that sits next to the
# index binary (which is just `qlever-upgrade-index` from the `PATH`,
# or from the container image, when the index binary is a plain
# `qlever-index`).
upgrade_index_binary = args.upgrade_index_binary
if upgrade_index_binary is None:
directory, slash, _ = args.index_binary.rpartition("/")
upgrade_index_binary = (
f"{directory}/qlever-upgrade-index"
if slash
else "qlever-upgrade-index"
)
Comment on lines +49 to +56

# Construct the command line.
upgrade_index_cmd = (
f"{upgrade_index_binary} {args.name}"
f" 2>&1 | tee {args.name}.upgrade-index-log.txt"
)

# Run the command in a container (if so desired).
if args.system in Containerize.supported_systems():
upgrade_index_cmd = Containerize().containerize_command(
upgrade_index_cmd,
args.system,
"run --rm",
args.image,
args.index_container,
volumes=[("$(pwd)", "/index")],
working_directory="/index",
)

# Show the command line.
self.show(upgrade_index_cmd, only_show=args.show)
if args.show:
return True

if not binary_exists(
upgrade_index_binary, "upgrade-index-binary", args
):
return False
Comment on lines +81 to +84

# Run the upgrade command.
try:
run_command(upgrade_index_cmd, show_output=True)
except Exception as e:
log.error(f"Upgrading the index failed: {e}")
return False

return True
159 changes: 159 additions & 0 deletions test/qlever/commands/test_upgrade_index_execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from __future__ import annotations

import unittest
from unittest.mock import MagicMock, patch

from qlever.commands.upgrade_index import UpgradeIndexCommand


def make_args() -> MagicMock:
args = MagicMock()
args.name = "TestName"
args.index_binary = "qlever-index"
args.upgrade_index_binary = None
args.system = "native"
args.image = "test_image"
args.index_container = "test_container"
args.show = False
return args


@patch("qlever.commands.upgrade_index.run_command")
@patch("qlever.commands.upgrade_index.binary_exists")
@patch("qlever.commands.upgrade_index.Containerize")
class TestUpgradeIndexCommand(unittest.TestCase):
# Native system, index binary from the PATH: the upgrade binary is
# `qlever-upgrade-index` from the PATH.
def test_execute_native(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = True

result = UpgradeIndexCommand().execute(args)

self.assertTrue(result)
mock_run_command.assert_called_once_with(
"qlever-upgrade-index TestName"
" 2>&1 | tee TestName.upgrade-index-log.txt",
show_output=True,
)

# Index binary given with a path: the upgrade binary is taken from the
# same directory.
def test_execute_binary_next_to_index_binary(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
args.index_binary = "/test/path/qlever-index"
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = True

result = UpgradeIndexCommand().execute(args)

self.assertTrue(result)
mock_run_command.assert_called_once_with(
"/test/path/qlever-upgrade-index TestName"
" 2>&1 | tee TestName.upgrade-index-log.txt",
show_output=True,
)

# An explicitly given `--upgrade-index-binary` is used as is.
def test_execute_explicit_binary(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
args.index_binary = "/test/path/qlever-index"
args.upgrade_index_binary = "/other/path/upgrade-binary"
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = True

result = UpgradeIndexCommand().execute(args)

self.assertTrue(result)
mock_run_command.assert_called_once_with(
"/other/path/upgrade-binary TestName"
" 2>&1 | tee TestName.upgrade-index-log.txt",
show_output=True,
)

# With a container system, the command is wrapped by `Containerize`.
def test_execute_containerized(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
args.system = "docker"
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = True
containerized_cmd = "docker run --rm ..."
containerize_instance = mock_containerize.return_value
containerize_instance.containerize_command.return_value = (
containerized_cmd
)

result = UpgradeIndexCommand().execute(args)

self.assertTrue(result)
containerize_instance.containerize_command.assert_called_once_with(
"qlever-upgrade-index TestName"
" 2>&1 | tee TestName.upgrade-index-log.txt",
"docker",
"run --rm",
args.image,
args.index_container,
volumes=[("$(pwd)", "/index")],
working_directory="/index",
)
mock_run_command.assert_called_once_with(
containerized_cmd, show_output=True
)

# With `--show`, the command is only shown, not run.
def test_execute_show(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
args.show = True
mock_containerize.supported_systems.return_value = ["docker"]

result = UpgradeIndexCommand().execute(args)

self.assertTrue(result)
mock_run_command.assert_not_called()

# A missing binary fails the command before anything is run.
def test_execute_binary_missing(
self, mock_containerize, mock_binary_exists, mock_run_command
):
args = make_args()
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = False

result = UpgradeIndexCommand().execute(args)

self.assertFalse(result)
mock_run_command.assert_not_called()

# A failing upgrade binary fails the command.
@patch("qlever.commands.upgrade_index.log")
def test_execute_upgrade_fails(
self,
mock_log,
mock_containerize,
mock_binary_exists,
mock_run_command,
):
args = make_args()
mock_containerize.supported_systems.return_value = ["docker"]
mock_binary_exists.return_value = True
mock_run_command.side_effect = Exception("upgrade failed")

result = UpgradeIndexCommand().execute(args)

self.assertFalse(result)
mock_log.error.assert_called_once()


if __name__ == "__main__":
unittest.main()
Loading