mirror of
https://github.com/netinvent/npbackup.git
synced 2025-09-05 20:44:51 +08:00
487 lines
16 KiB
Python
487 lines
16 KiB
Python
#! /usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
__intname__ = "npbackup_cli_tests"
|
|
__author__ = "Orsiris de Jong"
|
|
__copyright__ = "Copyright (C) 2022-2025 NetInvent"
|
|
__license__ = "BSD-3-Clause"
|
|
__build__ = "2024121001"
|
|
|
|
|
|
"""
|
|
Simple test where we launch the CLI and hope it doesn't die
|
|
Should be improved with much stronger tests
|
|
Missing:
|
|
- VSS test
|
|
- backup minimum size tests
|
|
- proper retention policy tests
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
from io import StringIO
|
|
import json
|
|
import requests
|
|
import tempfile
|
|
import bz2
|
|
from pprint import pprint
|
|
|
|
sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from npbackup import __main__
|
|
from npbackup.path_helper import BASEDIR
|
|
from npbackup.configuration import load_config, get_repo_config
|
|
from RESTIC_SOURCE_FILES.update_restic import download_restic_binaries_for_arch
|
|
|
|
|
|
if os.name == "nt":
|
|
ORIGINAL_CONF_FILE = "npbackup-cli-test-windows.yaml"
|
|
else:
|
|
ORIGINAL_CONF_FILE = "npbackup-cli-test-linux.yaml"
|
|
|
|
ORIGINAL_CONF_FILE_PATH = (
|
|
Path(BASEDIR).absolute().parent.joinpath("tests").joinpath(ORIGINAL_CONF_FILE)
|
|
)
|
|
CONF_FILE = Path(tempfile.gettempdir()).absolute().joinpath(ORIGINAL_CONF_FILE)
|
|
# Now that we got the path to the config file, we need to replace repo_uri with a temporary directory
|
|
# Danger: THIS WILL NEED SOME ADJUSTMENT FOR multi repo tests
|
|
|
|
temp_repo_dir = Path(tempfile.mkdtemp(prefix="npbackup_test_repo_"))
|
|
restore_dir = Path(tempfile.mkdtemp(prefix="npbackup_test_restore_"))
|
|
|
|
raw_config = ORIGINAL_CONF_FILE_PATH.read_text().replace(
|
|
"repo_uri: ./test", f"repo_uri: {temp_repo_dir}"
|
|
)
|
|
CONF_FILE.write_text(raw_config)
|
|
|
|
full_config = load_config(CONF_FILE)
|
|
repo_config, _ = get_repo_config(full_config)
|
|
|
|
|
|
# File we will request in dump mode
|
|
DUMP_FILE = "__version__.py"
|
|
|
|
|
|
class RedirectedStdout:
|
|
"""
|
|
Balantly copied from https://stackoverflow.com/a/45899925/2635443
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._stdout = None
|
|
self._string_io = None
|
|
|
|
def __enter__(self):
|
|
self._stdout = sys.stdout
|
|
sys.stdout = self._string_io = StringIO()
|
|
return self
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
sys.stdout = self._stdout
|
|
|
|
def __str__(self):
|
|
return self._string_io.getvalue()
|
|
|
|
|
|
def test_download_restic_binaries():
|
|
"""
|
|
We must first download latest restic binaries to make sure we can run all tests
|
|
Currently we only run these on amd64
|
|
"""
|
|
# We'll try to download restic binaries, but it may fail on github actions because of rate limiting
|
|
# so we allow failure for this test
|
|
result = download_restic_binaries_for_arch()
|
|
print("DOWNLOAD result: ", result)
|
|
|
|
|
|
def test_npbackup_cli_no_config():
|
|
sys.argv = [""] # Make sure we don't get any pytest args
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
__main__.main()
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "CRITICAL :: Cannot run without configuration file" in str(
|
|
logs
|
|
), "There should be a critical error when config file is not given"
|
|
|
|
|
|
def test_npbackup_cli_wrong_config_path():
|
|
sys.argv = ["", "-c", "npbackup-non-existent.conf"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
__main__.main()
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert (
|
|
"Config file npbackup-non-existent.conf cannot be read or does not exist"
|
|
in str(logs)
|
|
), "There should be a critical error when config file is not given"
|
|
|
|
|
|
def test_npbackup_cli_show_config():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--show-config"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
__main__.main()
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "__(o_O)__" in str(logs), "Obfuscation does not work"
|
|
|
|
|
|
def test_npbackup_cli_init():
|
|
shutil.rmtree(repo_config.g("repo_uri"), ignore_errors=True)
|
|
os.environ["_DEBUG"] = "True"
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--init"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "created restic repository" in str(logs), "Did not create repo"
|
|
assert "Repo initialized successfully" in str(logs), "Repo init failed"
|
|
os.environ["_DEBUG"] = "False"
|
|
|
|
def test_npbackup_cli_has_no_recent_snapshots():
|
|
"""
|
|
After init, we should not have recent snapshots
|
|
"""
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--has-recent-snapshot", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"] == False, "Should not have recent snapshots"
|
|
assert json_logs["operation"] == "has_recent_snapshot", "Bogus operation name, probably failed somewhere earlier"
|
|
|
|
|
|
def test_npbackup_cli_create_backup():
|
|
# Let's remove the repo before creating a backup since backup should auto init the repo
|
|
shutil.rmtree(repo_config.g("repo_uri"), ignore_errors=True)
|
|
sys.argv = ["", "-c", str(CONF_FILE), "-b"]
|
|
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "Backend finished with success" in str(logs), "Backup failed"
|
|
|
|
|
|
def test_npbackup_cli_has_recent_snapshots():
|
|
"""
|
|
After backup, we should have recent snapshots
|
|
"""
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--has-recent-snapshot", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"], "Should have recent snapshots"
|
|
|
|
|
|
def test_npbackup_cli_unlock():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--unlock"]
|
|
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "Repo successfully unlocked" in str(logs), "Could not unlock repo"
|
|
|
|
|
|
def test_npbackup_cli_snapshots():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--snapshots", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
__main__.main()
|
|
except SystemExit:
|
|
print(str(logs))
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"], "Bad snapshot result"
|
|
assert (
|
|
json_logs["operation"] == "snapshots"
|
|
), "Bogus operation name for snapshots"
|
|
assert len(json_logs["output"]) == 1, "More than one snapshot present"
|
|
|
|
|
|
def test_npbackup_cli_restore():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "-r", str(restore_dir)]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(str(logs))
|
|
assert "Successfully restored data" in str(
|
|
logs
|
|
), "Logs don't show successful restore"
|
|
assert Path(
|
|
f"{restore_dir}/npbackup/npbackup/__version__.py"
|
|
).is_file(), "Restored snapshot does not contain our data"
|
|
|
|
|
|
def test_npbackup_cli_ls():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--ls", "latest", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"], "Bad ls result"
|
|
assert json_logs["operation"] == "ls", "Bogus operation name for ls"
|
|
assert "/npbackup/npbackup/gui/__main__.py" in str(
|
|
logs
|
|
), "Missing main gui in list"
|
|
|
|
|
|
def test_npbackup_cli_list_snapshots():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--list", "snapshots", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"], "Bad list result"
|
|
assert json_logs["operation"] == "list", "Bogus operation name for list"
|
|
assert len(json_logs["output"]["data"]) == 64, "No snapshot data found"
|
|
|
|
|
|
def test_npbackup_cli_find():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--find", "__version__.py"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Found matching entries in snapshot" in str(
|
|
logs
|
|
), "Did not find match for find"
|
|
assert "__version__.py", "Did not find __version__.py in find"
|
|
|
|
|
|
def test_npbackup_cli_check_quick():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--check", "quick"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Running metadata consistency check of repository" in str(
|
|
logs
|
|
), "Failed quick checking repo"
|
|
print(logs)
|
|
assert "Repo checked successfully" in str(logs), "Quick check failed"
|
|
|
|
|
|
def test_npbackup_cli_check_full():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--check", "full"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Running full data check of repository" in str(
|
|
logs
|
|
), "Failed full checking repo"
|
|
print(logs)
|
|
assert "Repo checked successfully" in str(logs), "Full check failed"
|
|
|
|
|
|
def test_npbackup_cli_repair_index():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--repair", "index"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Repairing index in repo" in str(logs), "Index repair failed"
|
|
print(logs)
|
|
assert "Repo successfully repaired:" in str(logs), "Missing repair info"
|
|
|
|
|
|
def test_npbackup_cli_repair_snapshots():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--repair", "snapshots"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Repairing snapshots in repo" in str(logs), "Snapshot repair failed"
|
|
print(logs)
|
|
assert "Repo successfully repaired:" in str(logs), "Missing repair info"
|
|
|
|
|
|
def test_npbackup_cli_retention():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--policy"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Successfully applied retention policy" in str(
|
|
logs
|
|
), "Failed applying retention policy"
|
|
|
|
|
|
def test_npbackup_cli_forget():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--forget", "latest"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Forgetting snapshots ['latest']" in str(
|
|
logs
|
|
), "Could not forget snapshot"
|
|
assert "removed snapshot/" in str(logs), "Did not forget snapshot"
|
|
assert "Successfully forgot snapshot" in str(logs), "Forget failed"
|
|
|
|
|
|
def test_npbackup_cli_recover():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--recover"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Recovering snapshots in repo default" in str(
|
|
logs
|
|
), "Could not recover snapshots"
|
|
assert "found 1 unreferenced roots" in str(logs), "Should have found 1 snapshot"
|
|
assert "Recovery finished" in str(logs), "Recovery failed"
|
|
|
|
|
|
def test_npbackup_cli_prune():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--prune"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Pruning snapshots for repo" in str(logs), "Could not prune repo"
|
|
assert "unused size after prune" in str(logs), "Did not prune"
|
|
assert "Successfully pruned repository" in str(logs), "Prune failed"
|
|
|
|
|
|
def test_npbackup_cli_housekeeping():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--housekeeping", "--json"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
json_logs = json.loads(str(logs))
|
|
assert json_logs["result"], "Bad housekeeping result"
|
|
assert (
|
|
json_logs["operation"] == "housekeeping"
|
|
), "Bogus operation name for housekeeping"
|
|
assert json_logs["detail"]["unlock"]["result"], "Unlock failed in housekeeping"
|
|
assert json_logs["detail"]["check"]["result"], "check failed in housekeeping"
|
|
assert json_logs["detail"]["forget"]["result"], "forget failed in housekeeping"
|
|
assert (
|
|
len(json_logs["detail"]["forget"]["args"]["policy"]) > 4
|
|
), "policy missing in housekeeping"
|
|
assert json_logs["detail"]["prune"]["result"], "prune failed in housekeeping"
|
|
|
|
|
|
def test_npbackup_cli_raw():
|
|
global DUMP_FILE
|
|
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--raw", "ls latest"]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print(logs)
|
|
assert "Running raw command" in str(logs), "Did not run raw command"
|
|
assert "Successfully run raw command" in str(logs), "Did not run raw command"
|
|
assert DUMP_FILE in str(logs), "raw ls output should contain DUMP_FILE name"
|
|
for line in str(logs).split("\n"):
|
|
if DUMP_FILE in line:
|
|
DUMP_FILE = line
|
|
print("FOUND DUMP FILE", DUMP_FILE)
|
|
break
|
|
|
|
|
|
def test_npbackup_cli_dump():
|
|
sys.argv = ["", "-c", str(CONF_FILE), "--dump", DUMP_FILE]
|
|
try:
|
|
with RedirectedStdout() as logs:
|
|
e = __main__.main()
|
|
print(e)
|
|
except SystemExit:
|
|
print("DUMPED FILE", DUMP_FILE)
|
|
print(logs)
|
|
assert '__intname__ = "npbackup"' in str(logs), "version file seems bogus"
|
|
assert '"pv": sys.version_info,' in str(logs), "Version file still seems bogus"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_download_restic_binaries()
|
|
test_npbackup_cli_no_config()
|
|
test_npbackup_cli_wrong_config_path()
|
|
test_npbackup_cli_show_config()
|
|
|
|
test_npbackup_cli_init()
|
|
test_npbackup_cli_has_no_recent_snapshots()
|
|
|
|
# Backup process
|
|
test_npbackup_cli_create_backup()
|
|
test_npbackup_cli_has_recent_snapshots()
|
|
test_npbackup_cli_unlock()
|
|
test_npbackup_cli_snapshots()
|
|
test_npbackup_cli_restore()
|
|
test_npbackup_cli_list_snapshots()
|
|
# This one should is pretty hard to test without having repo with multiple different date snapshots
|
|
# We need to create a "fake" repo starting in let's say 2020 and put our date back to 2023 to test our standard
|
|
# policy
|
|
# We can also have a forget test which should fail because of bogus permissions
|
|
test_npbackup_cli_retention()
|
|
test_npbackup_cli_forget()
|
|
test_npbackup_cli_recover()
|
|
test_npbackup_cli_prune()
|
|
|
|
# basic tests for all other commands
|
|
test_npbackup_cli_ls()
|
|
test_npbackup_cli_find()
|
|
test_npbackup_cli_check_quick()
|
|
test_npbackup_cli_check_full()
|
|
test_npbackup_cli_repair_index()
|
|
test_npbackup_cli_repair_snapshots()
|
|
|
|
# Repairing packs needs pack ids
|
|
# test_npbackup_cli_repair_packs()
|
|
|
|
test_npbackup_cli_housekeeping()
|
|
|
|
test_npbackup_cli_raw()
|
|
test_npbackup_cli_dump()
|