1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2025-03-15 08:29:42 +00:00
* Simplify non-blocking BorgThread.run.
* Fix issue with displaying nested folders in extract-dialog.
* Fix error text expansion.
* Add many new tests. Dont open main window on startup.
This commit is contained in:
Manuel Riel 2018-11-30 08:40:18 +08:00 committed by GitHub
parent 74047e1dcb
commit b55c32517c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 577 additions and 245 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@ vorta.egg-info
.python-version
.vagrant
*.log
htmlcov

View file

@ -14,7 +14,7 @@ github-release: Vorta.dmg
hub release create --prerelease --attach=dist/vorta-0.5.0.dmg v0.5.0
git checkout gh-pages
git commit -m 'rebuild pages' --allow-empty
git push origin gh-pages
git push upstream gh-pages
git checkout master
pypi-release:

View file

@ -7,17 +7,12 @@ Vorta is an open source macOS/Linux GUI for [BorgBackup](https://borgbackup.read
## Main features
- Encrypted, deduplicated and compressed backups.
- Encrypted, deduplicated and compressed backups using `borg` as battle-tested backend.
- Works with any local or remote Borg repo. Try [BorgBase](https://www.borgbase.com) for advanced features like append-only repositories and monitoring.
- Add SSH keys and initialize repos directly from the GUI.
- Repo keys are securely stored in macOS Keychain, SecretService or KWallet.
- Mount existing archives via FUSE.
- Repo keys are securely stored in macOS Keychain, SecretService or KWallet. Create SSH keys directly from the GUI.
- Manage multiple backup profiles with different source folders, destinations and settings.
- Prune and check backups periodically.
- Flexible scheduling for automatic background backups. Only allow on certain Wifis.
- View a list of archives and action logs.
- Exclude options/patterns.
- Automatic updates using Sparkle (on macOS)
- Restore files from mounts or using the built-in backup browser.
## Installation and Download
Vorta should work on all platforms that support Qt and Borg. Currently Borg doesn't support Windows, but this may change in the future. Setting allowed Wifi networks is currently not supported on Linux, but everything else should work.

View file

@ -50,7 +50,7 @@ class VortaApp(QApplication):
# Prepare tray and main window
self.tray = TrayMenu(self)
self.main_window = MainWindow(self)
self.main_window.show()
# self.main_window.show()
self.backup_started_event.connect(self.backup_started_event_response)
self.backup_finished_event.connect(self.backup_finished_event_response)

View file

@ -257,7 +257,7 @@
<string>Extract</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/cloud-download.svg</normaloff>:/icons/cloud-download.svg</iconset>
</property>
</widget>
@ -274,7 +274,7 @@
<string>Mount</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/folder-open.svg</normaloff>:/icons/folder-open.svg</iconset>
</property>
</widget>
@ -285,7 +285,7 @@
<string>Check</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/check-circle.svg</normaloff>:/icons/check-circle.svg</iconset>
</property>
</widget>
@ -309,7 +309,7 @@
<string>Prune</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/cut.svg</normaloff>:/icons/cut.svg</iconset>
</property>
</widget>
@ -329,7 +329,7 @@
<string>Refresh</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/refresh.svg</normaloff>:/icons/refresh.svg</iconset>
</property>
</widget>

View file

@ -92,7 +92,7 @@
<item>
<widget class="QToolButton" name="profileRenameButton">
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
</property>
<property name="iconSize">
@ -109,7 +109,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/trash.svg</normaloff>:/icons/trash.svg</iconset>
</property>
</widget>

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>555</width>
<height>270</height>
<height>277</height>
</rect>
</property>
<property name="windowTitle">
@ -159,6 +159,12 @@
</item>
<item row="7" column="1">
<widget class="QLabel" name="errorText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
@ -170,9 +176,15 @@
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>

View file

@ -92,7 +92,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/unlink.svg</normaloff>:/icons/unlink.svg</iconset>
</property>
<property name="autoRaise">
@ -160,7 +160,7 @@
<string>Copy</string>
</property>
<property name="icon">
<iconset resource="../icons/collection.qrc">
<iconset>
<normaloff>:/icons/copy.svg</normaloff>:/icons/copy.svg</iconset>
</property>
<property name="checkable">

View file

@ -6,176 +6,179 @@
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>300</height>
<width>557</width>
<height>286</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>451</width>
<height>281</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Generate SSH Key</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Key Format:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="formatSelect">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Key Length:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="lengthSelect">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;2048 or 4096 for RSA, 384 or 521 for ECDSA. Fixed for Ed25519. &lt;a href=&quot;https://stribika.github.io/2015/01/04/secure-secure-shell.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;More&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="outputFileTextBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Output File:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Don't change this if you want SSH to automatically find the key.</string>
</property>
</widget>
</item>
</layout>
</item>
<item alignment="Qt::AlignBottom">
<widget class="QLabel" name="errors">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="generateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Generate and copy to Clipboard</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Generate SSH Key</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Key Format:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="formatSelect">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Key Length:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="lengthSelect">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;2048 or 4096 for RSA, 384 or 521 for ECDSA. Fixed for Ed25519. &lt;a href=&quot;https://stribika.github.io/2015/01/04/secure-secure-shell.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;More&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="outputFileTextBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Output File:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Don't change this if you want SSH to automatically find the key.</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="generateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Generate and copy to Clipboard</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QLabel" name="errors">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>

View file

@ -4,7 +4,6 @@ import sys
import shutil
import signal
import select
import fcntl
import time
import logging
from PyQt5 import QtCore
@ -145,26 +144,20 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
env=self.env, cwd=self.cwd, preexec_fn=os.setsid)
self.process = p
# Prevent blocking. via https://stackoverflow.com/a/7730201/3983708
# Prevent blocking of stdout/err. Via https://stackoverflow.com/a/7730201/3983708
os.set_blocking(p.stdout.fileno(), False)
os.set_blocking(p.stderr.fileno(), False)
# Helper function to add the O_NONBLOCK flag to a file descriptor
def make_async(fd):
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
# Helper function to read some data from a file descriptor, ignoring EAGAIN errors
def read_async(fd):
try:
return fd.read()
except (IOError, TypeError):
return ''
make_async(p.stdout)
make_async(p.stderr)
stdout = []
while True:
# Wait for new output
select.select([p.stdout, p.stderr], [], [])
select.select([p.stdout, p.stderr], [], [], 0.1)
stdout.append(read_async(p.stdout))
stderr = read_async(p.stderr)
@ -184,6 +177,7 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
logger.warning(msg)
if p.poll() is not None:
time.sleep(0.1)
stdout.append(read_async(p.stdout))
break
@ -203,10 +197,6 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
log_entry.repo_url = self.params.get('repo_url', None)
log_entry.save()
# Ensure async reading of mock stdout/stderr is finished.
if hasattr(sys, '_called_from_test'):
time.sleep(1)
self.process_result(result)
self.finished_event(result)
mutex.unlock()

View file

@ -256,6 +256,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin):
window = ExtractDialog(result['data'], archive)
self._toggle_all_buttons(True)
window.setParent(self, QtCore.Qt.Sheet)
self._window = window # for testing
window.show()
if window.exec_():

View file

@ -1,5 +1,8 @@
import sys
import os
import datetime
from collections import namedtuple
from PyQt5 import uic
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QHeaderView
@ -10,29 +13,30 @@ uifile = get_asset('UI/extractdialog.ui')
ExtractDialogUI, ExtractDialogBase = uic.loadUiType(uifile)
ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
full_list = []
folder_list = nested_dict()
selected = set()
files_with_attributes = []
nested_file_list = nested_dict()
selected_files_folders = set()
class ExtractDialog(ExtractDialogBase, ExtractDialogUI):
def __init__(self, fs_data, archive):
super().__init__()
self.setupUi(self)
global full_list, folder_list, selected
global files_with_attributes, nested_file_list, selected_files_folders
def parse_line(line):
size, modified, full_path = line.split('\t')
size = int(size)
dir, name = os.path.split(full_path)
if size == 0:
d = get_dict_from_list(folder_list, dir.split('/'))
if name not in d:
d[name] = {}
# add to nested dict of folders to find nested dirs.
d = get_dict_from_list(nested_file_list, dir.split('/'))
if name not in d:
d[name] = {}
return size, modified, name, dir
full_list = [parse_line(l) for l in fs_data.split('\n')[:-1]]
files_with_attributes = [parse_line(l) for l in fs_data.split('\n')[:-1]]
model = TreeModel()
@ -49,7 +53,7 @@ class ExtractDialog(ExtractDialogBase, ExtractDialogUI):
self.archiveNameLabel.setText(f'{archive.name}, {archive.time}')
self.cancelButton.clicked.connect(self.close)
self.extractButton.clicked.connect(self.accept)
self.selected = selected
self.selected = selected_files_folders
class FileItem:
@ -81,11 +85,11 @@ class FileItem:
def setCheckedState(self, value):
if value == 2:
self.checkedState = True
selected.add(
selected_files_folders.add(
os.path.join(self.parentItem.path, self.parentItem.data(0), self.itemData[0]))
else:
self.checkedState = False
selected.remove(
selected_files_folders.remove(
os.path.join(self.parentItem.path, self.parentItem.data(0), self.itemData[0]))
def getCheckedState(self):
@ -107,12 +111,15 @@ class FolderItem(FileItem):
self._filtered_children = []
search_path = os.path.join(self.path, name)
if parent is None: # Find path for root folder
for root_folder in folder_list.keys():
for root_folder in nested_file_list.keys():
self._filtered_children.append((0, '', root_folder, '', ))
else:
self._filtered_children = [f for f in full_list if search_path == f[3]]
if self.childCount() == 0: # If there are no immediate children, we try the next-deepest folder.
for immediate_child in get_dict_from_list(folder_list, search_path.split('/')).keys():
# This adds direct children.
self._filtered_children = [f for f in files_with_attributes if search_path == f[3]]
# Add nested folders.
for immediate_child in get_dict_from_list(nested_file_list, search_path.split('/')).keys():
if not [True for child in self._filtered_children if child[2] == immediate_child]:
self._filtered_children.append((0, '', immediate_child, search_path))
self.is_loaded = False
@ -254,8 +261,16 @@ class TreeModel(QAbstractItemModel):
if __name__ == '__main__':
"""
For local testing:
borg list --progress --info --log-json --format="{size:8d}{TAB}{mtime}{TAB}{path}{NL}"
"""
FakeArchive = namedtuple('Archive', ['name', 'time'])
app = QApplication(sys.argv)
test_list = open('/Users/manu/Downloads/archive_list.txt').read()
view = ExtractDialog(test_list.split('\n'))
test_list = open('/Users/manu/Downloads/nyx2-list.txt').read()
archive = FakeArchive('test-archive', datetime.datetime.now())
view = ExtractDialog(test_list, archive)
view.show()
sys.exit(app.exec_())

View file

@ -83,7 +83,7 @@ class MainWindow(MainWindowBase, MainWindowUI):
window = AddProfileWindow()
window.setParent(self, QtCore.Qt.Sheet)
window.show()
if window.exec_():
if window.exec_() and window.edited_profile:
self.profileSelector.addItem(window.edited_profile.name, window.edited_profile.id)
self.profileSelector.setCurrentIndex(self.profileSelector.count() - 1)
else:

View file

@ -82,6 +82,8 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
ssh_add_window.show()
if ssh_add_window.exec_():
self.init_ssh()
else:
self.sshComboBox.setCurrentIndex(0)
else:
profile = self.profile()
profile.ssh_key = self.sshComboBox.itemData(index)
@ -129,6 +131,9 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
window.show()
if window.exec_():
self.process_new_repo(window.result)
else:
self.repoSelector.setCurrentIndex(0)
else:
profile = self.profile()
profile.repo = self.repoSelector.currentData()

View file

@ -45,6 +45,7 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin):
item = "directory" if want_folder else "file"
dialog = choose_folder_dialog(self, "Choose %s to back up" % item, want_folder=want_folder)
self._file_dialog = dialog # for pytest
dialog.open(receive)
def source_remove(self):

View file

@ -0,0 +1,6 @@
{"name": "borg.repository", "message": "Remote: Starting repository check", "type": "log_message", "levelname": "INFO", "time": 1543501427.3991857}
{"name": "borg.repository", "message": "Remote: Starting repository index check", "type": "log_message", "levelname": "INFO", "time": 1543501428.2007568}
{"name": "borg.repository", "message": "Remote: Completed repository check, no problems found.", "type": "log_message", "levelname": "INFO", "time": 1543501428.201012}
{"type": "log_message", "time": 1543501427.141906, "message": "Starting archive consistency check...", "levelname": "INFO", "name": "borg.archive"}
{"type": "log_message", "time": 1543501429.413376, "message": "Analyzing archive nyx2.local-1-2018-11-26T15:33:25 (1/1)", "levelname": "INFO", "name": "borg.archive"}
{"type": "log_message", "time": 1543501430.631774, "message": "Archive consistency check complete, no problems found.", "levelname": "INFO", "name": "borg.archive"}

View file

@ -0,0 +1,6 @@
{"name": "borg.repository", "message": "Remote: Starting repository check", "type": "log_message", "levelname": "INFO", "time": 1543501427.3991857}
{"name": "borg.repository", "message": "Remote: Starting repository index check", "type": "log_message", "levelname": "INFO", "time": 1543501428.2007568}
{"name": "borg.repository", "message": "Remote: Completed repository check, no problems found.", "type": "log_message", "levelname": "INFO", "time": 1543501428.201012}
{"type": "log_message", "time": 1543501427.141906, "message": "Starting archive consistency check...", "levelname": "INFO", "name": "borg.archive"}
{"type": "log_message", "time": 1543501429.413376, "message": "Analyzing archive nyx2.local-1-2018-11-26T15:33:25 (1/1)", "levelname": "INFO", "name": "borg.archive"}
{"type": "log_message", "time": 1543501430.631774, "message": "Archive consistency check complete, no problems found.", "levelname": "INFO", "name": "borg.archive"}

View file

@ -0,0 +1,20 @@
{"type": "log_message", "time": 1541485706.185808, "message": "using builtin fallback logging configuration", "levelname": "DEBUG", "name": "borg.logger"}
{"type": "log_message", "time": 1541485706.329196, "message": "35 self tests completed in 0.14 seconds", "levelname": "DEBUG", "name": "borg.archiver"}
{"type": "log_message", "time": 1541485706.32982, "message": "SSH command line: ['ssh', 'w66xh7lj@w66xh7lj.repo.borgbase.com', 'borg', 'serve', '--umask=077', '--debug']", "levelname": "DEBUG", "name": "borg.remote"}
{"type": "log_message", "time": 1541485712.0352168, "message": "Remote: using builtin fallback logging configuration", "levelname": "DEBUG", "name": "borg.logger"}
{"type": "log_message", "time": 1541485712.10305, "message": "Remote: 35 self tests completed in 0.16 seconds", "levelname": "DEBUG", "name": "borg.archiver"}
{"type": "log_message", "time": 1541485711.9786682, "levelname": "DEBUG", "message": "Remote: using builtin fallback logging configuration", "name": "borg.logger"}
{"type": "log_message", "time": 1541485711.9788692, "levelname": "DEBUG", "message": "Remote: Initialized logging system for JSON-based protocol", "name": "borg.remote"}
{"type": "log_message", "time": 1541485712.2395813, "levelname": "DEBUG", "message": "Remote: Resolving repository path b'repo'", "name": "root"}
{"type": "log_message", "time": 1541485712.240386, "levelname": "DEBUG", "message": "Remote: Resolved repository path to '/srv/repos/w66xh7lj/repo'", "name": "root"}
{"type": "log_message", "time": 1541485712.7960937, "levelname": "DEBUG", "message": "Remote: Verified integrity of /srv/repos/w66xh7lj/repo/index.153", "name": "borg.crypto.file_integrity"}
{"type": "log_message", "time": 1541485713.763066, "message": "TAM-verified manifest", "levelname": "DEBUG", "name": "borg.crypto.key"}
{"type": "log_message", "time": 1541485713.779689, "message": "security: read previous location 'ssh://w66xh7lj@w66xh7lj.repo.borgbase.com/./repo'", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.780284, "message": "security: read manifest timestamp '2018-11-06T06:24:14.199720'", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.780406, "message": "security: determined newest manifest timestamp as 2018-11-06T06:24:14.199720", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.7819798, "message": "security: repository checks ok, allowing access", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.7866921, "message": "Verified integrity of /Users/manu/.cache/borg/daf2e2b94a1b57f0effc96939813ef58d0af04414f92f87c3e092a99adaa90eb/chunks", "levelname": "DEBUG", "name": "borg.crypto.file_integrity"}
{"type": "log_message", "time": 1541485713.7872949, "message": "security: read previous location 'ssh://w66xh7lj@w66xh7lj.repo.borgbase.com/./repo'", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.787873, "message": "security: read manifest timestamp '2018-11-06T06:24:14.199720'", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.788007, "message": "security: determined newest manifest timestamp as 2018-11-06T06:24:14.199720", "levelname": "DEBUG", "name": "borg.cache"}
{"type": "log_message", "time": 1541485713.788181, "message": "security: repository checks ok, allowing access", "levelname": "DEBUG", "name": "borg.cache"}

View file

@ -0,0 +1,69 @@
0 Thu, 2018-11-29 10:35:09 Users/manu/Desktop
0 Wed, 2013-09-04 00:54:20 Users/manu/Desktop/.ipynb_checkpoints
247348 Wed, 2013-09-04 01:00:31 Users/Untitled7-checkpoint.ipynb
888 Wed, 2018-03-21 23:18:32 Users/manu/Desktop/Receipts
800 Wed, 2018-03-21 23:18:58 Users/manu/Desktop/Documents
840 Tue, 2018-03-27 00:42:58 Users/manu/Desktop/travel
199 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.au/settings.sol
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.my
199 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.my/settings.sol
0 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com
196 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com/settings.sol
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.de
195 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.de/settings.sol
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tunescoop.com
194 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tunescoop.com/settings.sol
0 Fri, 2017-02-24 12:13:42 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.udemy.com
190 Fri, 2017-02-24 12:13:42 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.udemy.com/settings.sol
0 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ulozto.net
191 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ulozto.net/settings.sol
0 Fri, 2017-02-24 12:13:44 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ultimedia.com
194 Fri, 2017-02-24 12:13:44 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ultimedia.com/settings.sol
0 Fri, 2017-02-24 12:13:38 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.videodetective.net
199 Fri, 2017-02-24 12:13:38 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.videodetective.net/settings.sol
188016 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/NuFS.framework/Versions/A/NuFS
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/MacOS
229692 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/MacOS/transmitdiskfs
1908 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/Info.plist
339916 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/libfuse.dylib
47376 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/load_nufs
74416 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/mount_nufs
65853 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/License.pdf
2682 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Info.plist
551 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/version.plist
129893 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/disk-transmit.icns
1178 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/sparkle_dsa_pub.pem
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/_CodeSignature
8378 Fri, 2014-09-19 05:52:30 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/_CodeSignature/CodeResources
1768 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Info.plist
8 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/PkgInfo
239529 Thu, 2018-11-29 08:51:51 Users/manu/Library/Application Support/Transmit/Connections.transmitstore
0 Tue, 2018-11-13 09:02:33 Users/manu/Library/Application Support/coconutBattery
30939 Mon, 2017-09-11 22:52:42 Users/manu/Library/Application Support/coconutBattery/Mac.ccbarchive.backup_3.6.3
31097 Sat, 2017-04-08 18:45:35 Users/manu/Library/Application Support/coconutBattery/coconutBatteryData.ccbarchive
98162 Tue, 2018-11-13 09:02:33 Users/manu/Library/Application Support/coconutBattery/Mac.ccbarchive
84803 Tue, 2013-12-10 13:41:58 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2013-11-02.PDF
84228 Tue, 2013-12-10 13:42:02 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2013-12-02.PDF
85337 Tue, 2014-01-07 21:28:09 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-01-02.PDF
84228 Mon, 2014-04-07 22:48:28 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-02-02.PDF
85095 Mon, 2014-04-07 22:52:59 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-03-02.PDF
83329 Mon, 2014-04-07 22:53:03 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-04-02.PDF
0 Tue, 2016-03-15 19:29:23 Users/manu/Documents/financial/_archive/Direktanlage
117360 Tue, 2013-08-27 14:53:25 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300001.PDF
156800 Wed, 2013-10-16 20:21:38 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300002.PDF
80720 Sat, 2014-01-04 21:05:34 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300003.PDF
1292665 Mon, 2012-07-23 05:26:43 Users/manu/Documents/cooking/IMG_20120505_160225.jpg
1330876 Mon, 2012-07-23 05:26:40 Users/manu/Documents/cooking/IMG_20120505_160425.jpg
1219294 Mon, 2012-07-23 05:26:43 Users/manu/Documents/cooking/IMG_20120505_160842.jpg
1205672 Mon, 2012-07-23 05:26:44 Users/manu/Documents/cooking/IMG_20120505_161225.jpg
1254561 Mon, 2012-07-23 05:26:42 Users/manu/Documents/cooking/IMG_20120505_161247.jpg
1141842 Mon, 2012-07-23 05:26:44 Users/manu/Documents/cooking/IMG_20120505_161525.jpg
1143566 Mon, 2012-07-23 05:26:37 Users/manu/Documents/cooking/IMG_20120505_162547.jpg
1015435 Sat, 2012-02-25 21:55:12 Users/manu/Documents/cooking/Jamie Oliver Tuna in Tomatoe Sauce.pdf
33762 Fri, 2012-11-02 23:19:19 Users/manu/Documents/cooking/Mediterranean Beef Stew with Rosemary.pdf
1225647 Mon, 2012-07-23 05:26:50 Users/manu/Documents/cooking/Schweinekotelett und Ratatouille.jpg
1038737 Wed, 2012-12-12 21:38:43 Users/manu/Documents/cooking/Shrimp, Avocado and Red Pepper Salad.pdf
65393 Tue, 2012-11-13 22:04:04 Users/manu/Documents/cooking/Tiramisu Semifreddo.pdf
1266835 Mon, 2012-07-23 05:26:50 Users/manu/Documents/cooking/Tomaten-Paprika Suppe.jpg

View file

@ -0,0 +1,2 @@
{"levelname": "INFO", "message": "Remote: Storage quota: 10.51 MB out of 1.00 GB used.", "time": 1543489279.0468729, "type": "log_message", "name": "borg.repository"}
{"levelname": "INFO", "message": "Remote: Storage quota: 10.49 MB out of 1.00 GB used.", "time": 1543489282.1030364, "type": "log_message", "name": "borg.repository"}

View file

View file

@ -1,37 +1,54 @@
import pytest
import peewee
import sys
from datetime import datetime as dt
import vorta
from vorta.application import VortaApp
from vorta.models import RepoModel, SourceDirModel
from vorta.models import RepoModel, SourceDirModel, ArchiveModel, BackupProfileModel
def pytest_configure(config):
import sys
sys._called_from_test = True
@pytest.fixture()
@pytest.fixture
def app(tmpdir, qtbot):
tmp_db = tmpdir.join('settings.sqlite')
mock_db = peewee.SqliteDatabase(str(tmp_db))
vorta.models.init_db(mock_db)
new_repo = RepoModel(url='i0fi93@i593.repo.borgbase.com:repo')
new_repo.save()
profile = BackupProfileModel.get(id=1)
profile.repo = new_repo.id
profile.save()
test_archive = ArchiveModel(snapshot_id='99999', name='test-archive', time=dt(2000, 1, 1, 0, 0), repo=1)
test_archive.save()
source_dir = SourceDirModel(dir='/tmp', repo=new_repo)
source_dir.save()
app = VortaApp([])
qtbot.addWidget(app.main_window)
return app
@pytest.fixture()
def app_with_repo(app):
profile = app.main_window.current_profile
new_repo = RepoModel(url='i0fi93@i593.repo.borgbase.com:repo')
new_repo.save()
profile.repo = new_repo
profile.save()
@pytest.fixture
def choose_folder_dialog(*args):
class MockFileDialog:
def __init__(self, *args):
pass
source_dir = SourceDirModel(dir='/tmp', repo=new_repo)
source_dir.save()
return app
def open(self, func):
func()
def selectedFiles(self):
return ['/tmp']
return MockFileDialog
@pytest.fixture

View file

@ -1,5 +1,18 @@
import psutil
from collections import namedtuple
from PyQt5 import QtCore
from vorta.models import BackupProfileModel, ArchiveModel
import vorta.borg
import vorta.views.archive_tab
import vorta.utils
class MockFileDialog:
def open(self, func):
func()
def selectedFiles(self):
return ['/tmp']
def test_prune_intervals(app, qtbot):
@ -15,8 +28,8 @@ def test_prune_intervals(app, qtbot):
assert getattr(profile, f'prune_{i}') == 9
def test_repo_list(app_with_repo, qtbot, mocker, borg_json_output):
main = app_with_repo.main_window
def test_repo_list(app, qtbot, mocker, borg_json_output):
main = app.main_window
tab = main.archiveTab
main.tabWidget.setCurrentIndex(3)
tab.list_action()
@ -30,3 +43,94 @@ def test_repo_list(app_with_repo, qtbot, mocker, borg_json_output):
assert ArchiveModel.select().count() == 6
assert main.createProgressText.text() == 'Refreshing snapshots done.'
assert tab.checkButton.isEnabled()
def test_repo_prune(app, qtbot, mocker, borg_json_output):
main = app.main_window
tab = main.archiveTab
main.tabWidget.setCurrentIndex(3)
tab.populate_from_profile()
stdout, stderr = borg_json_output('prune')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
qtbot.mouseClick(tab.pruneButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: main.createProgressText.text().startswith('Refreshing snapshots'))
def test_check(app, mocker, borg_json_output, qtbot):
main = app.main_window
tab = main.archiveTab
main.tabWidget.setCurrentIndex(3)
tab.populate_from_profile()
stdout, stderr = borg_json_output('check')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
qtbot.mouseClick(tab.checkButton, QtCore.Qt.LeftButton)
success_text = 'INFO: Archive consistency check complete'
qtbot.waitUntil(lambda: main.createProgressText.text().startswith(success_text))
def test_archive_mount(app, qtbot, mocker, borg_json_output, monkeypatch, choose_folder_dialog):
def psutil_disk_partitions(**kwargs):
DiskPartitions = namedtuple('DiskPartitions', ['device', 'mountpoint'])
return [DiskPartitions('borgfs', '/tmp')]
monkeypatch.setattr(
psutil, "disk_partitions", psutil_disk_partitions
)
main = app.main_window
tab = main.archiveTab
main.tabWidget.setCurrentIndex(3)
tab.populate_from_profile()
tab.archiveTable.selectRow(0)
stdout, stderr = borg_json_output('prune')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
monkeypatch.setattr(
vorta.views.archive_tab, "choose_folder_dialog", choose_folder_dialog
)
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Mounted'), timeout=1000)
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
# qtbot.waitUntil(lambda: tab.mountErrors.text() == 'No active Borg mounts found.')
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Un-mounted successfully.'), timeout=5000)
def test_archive_extract(app, qtbot, mocker, borg_json_output, monkeypatch):
main = app.main_window
tab = main.archiveTab
main.tabWidget.setCurrentIndex(3)
tab.populate_from_profile()
qtbot.waitUntil(lambda: tab.archiveTable.rowCount() == 1)
qtbot.mouseClick(tab.extractButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Select an archive'))
monkeypatch.setattr(
vorta.views.extract_dialog.ExtractDialog, "exec_", lambda *args: True
)
tab.archiveTable.selectRow(0)
stdout, stderr = borg_json_output('list_archive')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
qtbot.mouseClick(tab.extractButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: hasattr(tab, '_window'))
assert tab._window.treeView.model().rootItem.childItems[0].data(0) == 'Users'
tab._window.treeView.model().rootItem.childItems[0].load_children()
assert tab._window.archiveNameLabel.text().startswith('test-archive, 2000')
tab._window.accept()

18
tests/test_borg.py Normal file
View file

@ -0,0 +1,18 @@
import vorta.borg
import vorta.models
from vorta.borg.prune import BorgPruneThread
def test_borg_prune(app, qtbot, mocker, borg_json_output):
stdout, stderr = borg_json_output('prune')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
params = BorgPruneThread.prepare(vorta.models.BackupProfileModel.select().first())
thread = BorgPruneThread(params['cmd'], params, app)
with qtbot.waitSignal(thread.result, timeout=10000) as blocker:
blocker.connect(thread.updated)
thread.run()
assert blocker.args[0]['returncode'] == 0

View file

@ -1,17 +1,14 @@
import os
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMessageBox
import vorta.borg.borg_thread
import vorta.models
from vorta.views.repo_add_dialog import AddRepoWindow
from vorta.views.ssh_dialog import SSHAddWindow
from vorta.models import EventLogModel, RepoModel, ArchiveModel
def test_create_fail(app, qtbot):
main = app.main_window
qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton)
assert main.createProgressText.text() == 'Add a backup repository first.'
def test_repo_add(app, qtbot, mocker, borg_json_output):
# Add new repo window
main = app.main_window
@ -38,11 +35,50 @@ def test_repo_add(app, qtbot, mocker, borg_json_output):
main.repoTab.process_new_repo(blocker.args[0])
# assert EventLogModel.select().count() == 2
assert RepoModel.get(id=1).url == 'aaabbb.com:repo'
assert RepoModel.get(id=2).url == 'aaabbb.com:repo'
def test_create(app_with_repo, borg_json_output, mocker, qtbot):
main = app_with_repo.main_window
def test_repo_unlink(app, qtbot, monkeypatch):
monkeypatch.setattr(QMessageBox, "exec_", lambda *args: QMessageBox.Yes)
main = app.main_window
tab = main.repoTab
main.tabWidget.setCurrentIndex(0)
qtbot.mouseClick(tab.repoRemoveToolbutton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: tab.repoSelector.count() == 3, timeout=5000)
assert tab.repoSelector.count() == 3
assert RepoModel.select().count() == 0
qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton)
assert main.createProgressText.text() == 'Add a backup repository first.'
def test_ssh_dialog(qtbot, tmpdir):
ssh_dialog = SSHAddWindow()
ssh_dir = tmpdir
key_tmpfile = ssh_dir.join("id_rsa-test")
pub_tmpfile = ssh_dir.join("id_rsa-test.pub")
key_tmpfile_full = os.path.join(key_tmpfile.dirname, key_tmpfile.basename)
ssh_dialog.outputFileTextBox.setText(key_tmpfile_full)
qtbot.mouseClick(ssh_dialog.generateButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: key_tmpfile.check(file=1))
key_tmpfile_content = key_tmpfile.read()
pub_tmpfile_content = pub_tmpfile.read()
assert key_tmpfile_content.startswith('-----BEGIN OPENSSH PRIVATE KEY-----')
assert pub_tmpfile_content.startswith('ssh-ed25519')
qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('New key was copied'))
clipboard = QApplication.clipboard()
assert clipboard.text().startswith('ssh-ed25519')
qtbot.mouseClick(ssh_dialog.generateButton, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('Key file already'))
def test_create(app, borg_json_output, mocker, qtbot):
main = app.main_window
stdout, stderr = borg_json_output('create')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
@ -51,8 +87,8 @@ def test_create(app_with_repo, borg_json_output, mocker, qtbot):
qtbot.waitUntil(lambda: main.createProgressText.text().startswith('Backup finished.'), timeout=3000)
qtbot.waitUntil(lambda: main.createStartBtn.isEnabled(), timeout=3000)
assert EventLogModel.select().count() == 1
assert ArchiveModel.select().count() == 1
assert ArchiveModel.select().count() == 2
assert RepoModel.get(id=1).unique_size == 15520474
assert main.createStartBtn.isEnabled()
assert main.archiveTab.archiveTable.rowCount() == 1
assert main.archiveTab.archiveTable.rowCount() == 2
assert main.scheduleTab.logTableWidget.rowCount() == 1

12
tests/test_scheduler.py Normal file
View file

@ -0,0 +1,12 @@
import vorta.borg
import vorta.models
def test_scheduler_create_backup(app, qtbot, mocker, borg_json_output):
stdout, stderr = borg_json_output('create')
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
app.scheduler.create_backup(1)
qtbot.waitUntil(lambda: vorta.models.EventLogModel.select().count() == 2, timeout=5000)

19
tests/test_source.py Normal file
View file

@ -0,0 +1,19 @@
import logging
from PyQt5 import QtCore
import vorta.models
def test_add_folder(app, qtbot, tmpdir):
main = app.main_window
main.tabWidget.setCurrentIndex(1)
tab = main.sourceTab
qtbot.mouseClick(tab.sourceAddFolder, QtCore.Qt.LeftButton)
qtbot.waitUntil(lambda: len(tab._file_dialog.selectedFiles()) > 0, timeout=3000)
tab._file_dialog.accept()
qtbot.waitUntil(lambda: tab.sourceDirectoriesWidget.count() == 2)
for src in vorta.models.SourceDirModel.select():
logging.error(src.dir, src.profile)