diff --git a/.travis.yml b/.travis.yml
index cf0516fea..a92b3946c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -57,9 +57,9 @@ jobs:
python: "3.12"
install:
- - pip install pylint coveralls pyfakefs
+ - pip install pylint coveralls pyfakefs keyring
# PyQt is not available for "ppc64le" architecture on PyPi
- - if [ "$TRAVIS_ARCH" != "ppc64le" ] ; then pip install pyqt6; fi
+ - if [ "$TRAVIS_ARCH" != "ppc64le" ] ; then pip install pyqt6 dbus-python; fi
# add ssh public / private key pair to ensure user can start ssh session to localhost for tests
- ssh-keygen -b 2048 -t rsa -f /home/travis/.ssh/id_rsa -N ""
- cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dd29ded6b..c85f27e95 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -90,8 +90,8 @@ replaced with PyPi packages.
- `python3-dbus.mainloop.pyqt6`
- `libnotify-bin`
- `policykit-1`
- - `qttranslations5-l10n`
- - `qtwayland5` (if Wayland is used as display server instead of X11)
+ - `qttranslations6-l10n`
+ - `qtwayland6` (if Wayland is used as display server instead of X11)
- Recommended
- For SSH key storage **one** of these packages
- `python3-secretstorage`
@@ -151,4 +151,4 @@ Keep in mind as you contribute, that code, docs and other material submitted
to the project are considered licensed under the same terms (see
[LICENSE](LICENSE)) as the rest of the work.
-Sept 2023
+March 2024
diff --git a/README.md b/README.md
index d9d1885f3..f2182d6a1 100644
--- a/README.md
+++ b/README.md
@@ -7,21 +7,15 @@
Copyright (C) 2008-2024 Oprea Dan, Bart de Koning, Richard Bailey,
Germar Reitze, Taylor Raack, Christian Buhtz, Michael Büker, Jürgen Altfeld
-_Back In Time_ is an easy-to-use backup tool for files and folders.
+_Back In Time_ is an easy-to-use tool to backup files and folders.
It runs on GNU Linux (not on Windows or OS X/macOS) and provides a command line tool `backintime` and a
GUI `backintime-qt` both written in Python3. It uses
[`rsync`](https://rsync.samba.org/) to take manual or scheduled snapshots and
stores them locally or remotely through SSH. Each snapshot is in its own folder
with copies of the original files, but unchanged files are hard-linked between
-snapshots to save space.
+snapshots to save storage space.
It was inspired by [FlyBack](https://en.wikipedia.org/wiki/FlyBack).
-You only need to specify 3 things:
-
-* What folders to back up.
-* Where to save snapshots.
-* The backup frequency (manual, every hour, every day, every month).
-
## Maintenance status
A small team (Christian Buhtz, Michael Büker and Jürgen Altfeld)
@@ -37,7 +31,7 @@ instead of implementing new
If you are interested in the development, please
see [CONTRIBUTING](CONTRIBUTING.md) and have a look on
[open issues](https://github.com/bit-team/backintime/issues) especially
-those labeled as [good first](https://github.com/bit-team/backintime/labels/GOOD%20FIRST%20ISSUE)
+those labeled as [good first issue](https://github.com/bit-team/backintime/labels/GOOD%20FIRST%20ISSUE)
and [help wanted](https://github.com/bit-team/backintime/issues?q=is%3Aissue+is%3Aopen+label%3AHELP-WANTED).
## Index
@@ -49,14 +43,15 @@ and [help wanted](https://github.com/bit-team/backintime/issues?q=is%3Aissue+is%
## Documentation, FAQs, Support
- * [End user documentation](https://backintime.readthedocs.org/) (not totally up-to-date)
* [FAQ - Frequently Asked Questions](FAQ.md)
- * [Source code documentation for developers](https://backintime-dev.readthedocs.org)
- * Use [Issues](https://github.com/bit-team/backintime/issues) to ask questions and report bugs.
+ * [End user documentation](https://backintime.readthedocs.org/) (not totally up-to-date)
* [Mailing list
_bit-dev_](https://mail.python.org/mailman3/lists/bit-dev.python.org/) for
**every topic**, question and idea about _Back In Time_. Despite its name
it is not restricted to development topics only.
+ * Use [Issues](https://github.com/bit-team/backintime/issues) to ask
+ questions and report bugs.
+ * [Source code documentation for developers](https://backintime-dev.readthedocs.org)
## Installation
@@ -77,11 +72,11 @@ installation options provided and maintained by third parties.
In the latest stable release:
- [File permissions handling and therefore possible non-differential backups](#file-permissions-handling-and-therefore-possible-non-differential-backups)
-- RTE "module 'qttools' has no attribute 'initate_translator'" with encFS when prompting the user for a password (#1553)
- [Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).](#warning-apt-key-is-deprecated-manage-keyring-files-in-trustedgpgd-instead-see-apt-key8)
- [`qt5_probing.py` may hang with high CPU usage when running BiT as `root` via `cron`](#qt5_probingpy-may-hang-with-high-cpu-usage-when-running-bit-as-root-via-cron)
In older releases:
+- RTE "module 'qttools' has no attribute 'initate_translator'" with encFS when prompting the user for a password ([#1553](https://github.com/bit-team/backintime/issues/#1553))
- [Tray icon or other icons not shown correctly](#tray-icon-or-other-icons-not-shown-correctly)
- [Non-working password safe and BiT forgets passwords (keyring backend issues)](#non-working-password-safe-and-bit-forgets-passwords-keyring-backend-issues)
- [Incompatibility with rsync >= 3.2.4](#incompatibility-with-rsync-324-or-newer)
@@ -120,7 +115,7 @@ This issue is tracked in [#1338](https://github.com/bit-team/backintime/issues/1
#### `qt5_probing.py` may hang with high CPU usage when running BiT as `root` via `cron`
-See the related issue #1592
+See the related issue [#1592](https://github.com/bit-team/backintime/issues/1592).
The only reliable work-around is to delete (or move into another folder)
the file `/usr/share/backintime/common/qt5_probing.py`:
@@ -184,15 +179,25 @@ See also issue [#1321](https://github.com/bit-team/backintime/issues/1321)
#### Incompatibility with rsync 3.2.4 or newer
-The release (`1.3.2`) and earlier versions of _Back In Time_ are incompatible with `rsync >= 3.2.4` ([#1247](https://github.com/bit-team/backintime/issues/1247)). The problem is [fixed](https://github.com/bit-team/backintime/pull/1351) in the current master branch of that repo and will be released with the next release (`1.3.3`) of _Back In Time_.
+**Status: Fixed in v1.3.3**
+
+The release (`1.3.2`) and earlier versions of _Back In Time_ are incompatible
+with `rsync >= 3.2.4`
+([#1247](https://github.com/bit-team/backintime/issues/1247)).
-If you use `rsync >= 3.2.4` and `backintime <= 1.3.2` there is a workaround. Add `--old-args` in [_Expert Options_ / _Additional options to rsync_](https://backintime.readthedocs.io/en/latest/settings.html#expert-options). Note that some GNU/Linux distributions (e.g. Manjaro) using a workaround with environment variable `RSYNC_OLD_ARGS` in their distro-specific packages for _Back In Time_. In that case you may not see any problems.
+If you use `rsync >= 3.2.4` and `backintime <= 1.3.2` there is a
+workaround. Add `--old-args` in
+[_Expert Options_ / _Additional options to rsync_](https://backintime.readthedocs.io/en/latest/settings.html#expert-options).
+Note that some GNU/Linux distributions (e.g. Manjaro) using a workaround with
+environment variable `RSYNC_OLD_ARGS` in their distro-specific packages for
+_Back In Time_. In that case you may not see any problems.
#### Python 3.10 compatibility and Ubuntu version
_Back In Time_ versions older than 1.3.2 do not start with Python >= 3.10.
Ubuntu 22.04 LTS ships with Python 3.10 and backintime 1.2.1, but has applied
[a patch](https://bugs.launchpad.net/ubuntu/+source/backintime/+bug/1976164/+attachment/5593556/+files/backintime_1.2.1-3_1.2.1-3ubuntu0.1.diff)
-to make it work. If you want to update to backintime 1.3.2 in Ubuntu, you may use the PPA: see under [`INSTALL/Ubuntu PPA`](#Ubuntu-PPA).
+to make it work. If you want to update _Back In Time_, you may use one of the
+[alternative options for installation](#alternative-installation-options).
-Jan 2024
+March 2024
diff --git a/common/backintime.py b/common/backintime.py
index b2ca974de..9ff047f55 100644
--- a/common/backintime.py
+++ b/common/backintime.py
@@ -37,7 +37,7 @@
import password
import encfstools
import cli
-from diagnostics import collect_diagnostics
+from diagnostics import collect_diagnostics, collect_minimal_diagnostics
from exceptions import MountException
from applicationinstance import ApplicationInstance
from version import __version__
@@ -504,14 +504,18 @@ def startApp(app_name = 'backintime'):
logger.openlog()
- #parse args
args = argParse(None)
- #add source path to $PATH environ if running from source
+ # Name, Version, As Root, OS
+ diag = collect_minimal_diagnostics()
+ logger.debug(
+ f'{diag["backintime"]} {list(diag["host-setup"]["OS"].values())}')
+
+ # Add source path to $PATH environ if running from source
if tools.runningFromSource():
tools.addSourceToPathEnviron()
- #warn about sudo
+ # Warn about sudo
if tools.usingSudo() and os.getenv('BIT_SUDO_WARNING_PRINTED', 'false') == 'false':
os.putenv('BIT_SUDO_WARNING_PRINTED', 'true')
logger.warning("It looks like you're using 'sudo' to start %(app)s. "
@@ -519,9 +523,10 @@ def startApp(app_name = 'backintime'):
"or 'pkexec %(app_name)s'."
%{'app_name': app_name, 'app': config.Config.APP_NAME})
- #call commands
+ # Call commands
if 'func' in dir(args):
args.func(args)
+
else:
setQuiet(args)
printHeader()
@@ -550,51 +555,64 @@ def join(args, subArgs):
that should be merged into ``args``
"""
for key, value in vars(subArgs).items():
- #only add new values if it isn't set already or if there really IS a value
+ # Only add new values if it isn't set already or if there really IS
+ # a value
if getattr(args, key, None) is None or value:
setattr(args, key, value)
- #first parse the main parser without subparsers
- #otherwise positional args in subparsers will be to greedy
- #but only if -h or --help is not involved because otherwise
- #help will not work for subcommands
+ # First parse the main parser without subparsers
+ # otherwise positional args in subparsers will be to greedy
+ # but only if -h or --help is not involved because otherwise
+ # help will not work for subcommands
mainParser = parsers['main']
sub = []
+
if '-h' not in sys.argv and '--help' not in sys.argv:
+
for i in mainParser._actions:
+
if isinstance(i, argparse._SubParsersAction):
- #remove subparsers
+ # Remove subparsers
mainParser._remove_action(i)
sub.append(i)
+
args, unknownArgs = mainParser.parse_known_args(args)
- #read subparsers again
+
+ # Read subparsers again
if sub:
[mainParser._add_action(i) for i in sub]
- #parse it again for unknown args
+ # Parse it again for unknown args
if unknownArgs:
subArgs, unknownArgs = mainParser.parse_known_args(unknownArgs)
join(args, subArgs)
- #finally parse only the command parser, otherwise we miss
- #some arguments from command
+ # Finally parse only the command parser, otherwise we miss some arguments
+ # from command
if unknownArgs and 'command' in args and args.command in parsers:
commandParser = parsers[args.command]
subArgs, unknownArgs = commandParser.parse_known_args(unknownArgs)
join(args, subArgs)
- if 'debug' in args:
+ try:
logger.DEBUG = args.debug
-
- dargs = vars(args)
- logger.debug('Arguments: %s | unknownArgs: %s'
- %({arg:dargs[arg] for arg in dargs if dargs[arg]},
- unknownArgs))
-
- #report unknown arguments
- #but not if we run aliasParser next because we will parse again in there
+ except AttributeError:
+ pass
+
+ args_dict = vars(args)
+ used_args = {
+ key: args_dict[key]
+ for key
+ in filter(lambda key: args_dict[key] is not None, args_dict)
+ }
+ logger.debug(f'Used argument(s): {used_args}')
+ logger.debug(f'Unknown argument(s): {unknownArgs}')
+
+ # Report unknown arguments but not if we run aliasParser next because we
+ # will parse again in there.
if unknownArgs and not ('func' in args and args.func is aliasParser):
- mainParser.error('Unknown Argument(s): %s' % ', '.join(unknownArgs))
+ mainParser.error(f'Unknown argument(s): {unknownArgs}')
+
return args
def printHeader():
diff --git a/common/config.py b/common/config.py
index 1d01f52a6..f7776784a 100644
--- a/common/config.py
+++ b/common/config.py
@@ -176,6 +176,7 @@ def __init__(self, config_path=None, data_path=None):
tools.makeDirs(self._LOCAL_MOUNT_ROOT)
self._DEFAULT_CONFIG_PATH = os.path.join(self._LOCAL_CONFIG_FOLDER, 'config')
+
if config_path is None:
self._LOCAL_CONFIG_PATH = self._DEFAULT_CONFIG_PATH
else:
diff --git a/common/diagnostics.py b/common/diagnostics.py
index 8fcb74731..9b476a1cc 100644
--- a/common/diagnostics.py
+++ b/common/diagnostics.py
@@ -4,7 +4,6 @@
paths, operating system and the like. This is used to enhance error reports
and to enrich them with the necessary information as uncomplicated as possible.
"""
-
import sys
import os
import itertools
@@ -20,6 +19,24 @@
import version
+def collect_minimal_diagnostics():
+ """Collect minimal information about backintime and the operating system.
+
+ Returns:
+ dict: A nested dictionary.
+ """
+ return {
+ 'backintime': {
+ 'name': config.Config.APP_NAME,
+ 'version': version.__version__,
+ 'running-as-root': pwd.getpwuid(os.getuid()) == 'root'
+ },
+ 'host-setup': {
+ 'OS': _get_os_release()
+ }
+ }
+
+
def collect_diagnostics():
"""Collect information about environment, versions of tools and
packages used by Back In Time.
@@ -29,9 +46,7 @@ def collect_diagnostics():
Returns:
dict: A nested dictionary.
"""
- result = {}
-
- pwd_struct = pwd.getpwuid(os.getuid())
+ result = collect_minimal_diagnostics()
# === BACK IN TIME ===
@@ -39,9 +54,7 @@ def collect_diagnostics():
# (should be singleton)
cfg = config.Config()
- result['backintime'] = {
- 'name': config.Config.APP_NAME,
- 'version': version.__version__,
+ result['backintime'].update({
'latest-config-version': config.Config.CONFIG_VERSION,
'local-config-file': cfg._LOCAL_CONFIG_PATH,
'local-config-file-found': Path(cfg._LOCAL_CONFIG_PATH).exists(),
@@ -49,10 +62,9 @@ def collect_diagnostics():
'global-config-file-found': Path(cfg._GLOBAL_CONFIG_PATH).exists(),
# 'distribution-package': str(distro_path),
'started-from': str(Path(config.__file__).parent),
- 'running-as-root': pwd_struct.pw_name == 'root',
'user-callback': cfg.takeSnapshotUserCallback(),
'keyring-supported': tools.keyringSupported()
- }
+ })
# Git repo
bit_root_path = Path(tools.backintimePath(""))
@@ -66,15 +78,12 @@ def collect_diagnostics():
result['backintime'][f'git-{key}'] = git_info[key]
# == HOST setup ===
- result['host-setup'] = {
+ result['host-setup'].update({
# Kernel & Architecture
'platform': platform.platform(),
# OS Version (and maybe name)
- 'system': '{} {}'.format(platform.system(), platform.version()),
- # OS Release name (prettier)
- 'OS': _get_os_release()
-
- }
+ 'system': f'{platform.system()} {platform.version()}'
+ })
# Display system (X11 or Wayland)
# This doesn't catch all edge cases.
@@ -101,20 +110,21 @@ def collect_diagnostics():
result['host-setup'][var] = os.environ.get(var, '(not set)')
# === PYTHON setup ===
- python = '{} {} {} {}'.format(
+ python = ' '.join((
platform.python_version(),
' '.join(platform.python_build()),
platform.python_implementation(),
platform.python_compiler()
- )
+ ))
# Python branch and revision if available
branch = platform.python_branch()
if branch:
- python = '{} branch: {}'.format(python, branch)
+ python = f'{python} branch: {branch}'
+
rev = platform.python_revision()
if rev:
- python = '{} rev: {}'.format(python, rev)
+ python = f'{python} rev: {rev}'
python_executable = Path(sys.executable)
@@ -190,8 +200,10 @@ def collect_diagnostics():
result['external-programs']['shell-version'] \
= shell_version.split('\n')[0]
- result = _replace_username_paths(result=result,
- username=pwd_struct.pw_name)
+ result = _replace_username_paths(
+ result=result,
+ username=pwd.getpwuid(os.getuid()).pw_name
+ )
return result
@@ -202,6 +214,7 @@ def _get_qt_information():
If environment variable ``DISPLAY`` is set a temporary QApplication
instances is created.
"""
+ # pylint: disable=import-outside-toplevel
try:
import PyQt6.QtCore
import PyQt6.QtGui
@@ -223,8 +236,8 @@ def _get_qt_information():
qapp.quit()
return {
- 'Version': 'PyQt {} / Qt {}'.format(PyQt6.QtCore.PYQT_VERSION_STR,
- PyQt6.QtCore.QT_VERSION_STR),
+ 'Version': f'PyQt {PyQt6.QtCore.PYQT_VERSION_STR} '
+ f'/ Qt {PyQt6.QtCore.QT_VERSION_STR}',
**theme_info
}
diff --git a/common/plugins/usercallbackplugin.py b/common/plugins/usercallbackplugin.py
index edb8ab1fd..69831409e 100644
--- a/common/plugins/usercallbackplugin.py
+++ b/common/plugins/usercallbackplugin.py
@@ -68,7 +68,7 @@ def init(self, snapshots):
return os.path.exists(self.script)
# TODO 09/28/2022: This method should be private (_callback)
- def callback(self, *args, profileID = None):
+ def callback(self, *args, profileID=None):
if profileID is None:
profileID = self.config.currentProfile()
diff --git a/common/qt_probing.py b/common/qt_probing.py
index b46383200..fe3a1cede 100644
--- a/common/qt_probing.py
+++ b/common/qt_probing.py
@@ -101,9 +101,8 @@
# os.seteuid(1000)
# logger.debug(f"New euid: {os.geteuid()}")
- # Disable pylint "import-error" because of TravisCI ppc64le architecture
- from PyQt6 import QtCore # pylint: disable=import-error
- from PyQt6.QtWidgets import QApplication # pylint: disable=import-error
+ from PyQt6 import QtCore
+ from PyQt6.QtWidgets import QApplication
app = QApplication([''])
@@ -118,7 +117,7 @@
# ("GUI") is active at all (e.g. in headless installations it isn't).
# See: https://forum.qt.io/topic/3852/issystemtrayavailable-always-crashes-segfault-on-ubuntu-10-10-desktop/6
- from PyQt6.QtWidgets import QSystemTrayIcon # pylint: disable=import-error
+ from PyQt6.QtWidgets import QSystemTrayIcon
is_sys_tray_available = QSystemTrayIcon.isSystemTrayAvailable()
if is_sys_tray_available:
diff --git a/common/test/test_diagnostics.py b/common/test/test_diagnostics.py
index 3d947514c..e611d1664 100644
--- a/common/test/test_diagnostics.py
+++ b/common/test/test_diagnostics.py
@@ -1,3 +1,4 @@
+"""Test related to diagnostics.py"""
import sys
import pathlib
import unittest
@@ -8,15 +9,31 @@
class Diagnostics(unittest.TestCase):
+ """Test about collecting diagnostic infos."""
- def test_minimal(self):
+ def test_content_minimal(self):
"""Minimal set of elements."""
+ sut = diagnostics.collect_minimal_diagnostics()
+
+ # 1st level keys
+ self.assertCountEqual(sut.keys(), ['backintime', 'host-setup'])
+
+ # 2nd level "backintime"
+ self.assertCountEqual(
+ sut['backintime'].keys(),
+ ['name', 'version', 'running-as-root'])
+
+ # 2nd level "host-setup"
+ self.assertCountEqual(sut['host-setup'].keys(), ['OS'])
+
+ def test_some_content(self):
+ """Some containted elements"""
result = diagnostics.collect_diagnostics()
# 1st level keys
- self.assertEqual(
- sorted(result.keys()),
+ self.assertCountEqual(
+ result.keys(),
['backintime', 'external-programs', 'host-setup', 'python-setup']
)
@@ -59,8 +76,7 @@ def test_no_ressource_warning(self):
diagnostics.collect_diagnostics()
def test_no_extern_version(self):
- """Get version from not existing tool.
- """
+ """Get version from not existing tool."""
self.assertEqual(
diagnostics._get_extern_versions(['fooXbar']),
'(no fooXbar)'
@@ -68,7 +84,6 @@ def test_no_extern_version(self):
def test_replace_user_path(self):
"""Replace users path."""
-
d = {
'foo': '/home/rsync',
'bar': '~/rsync'
@@ -86,5 +101,3 @@ def test_replace_user_path(self):
diagnostics._replace_username_paths(d, 'user'),
d
)
-
-
diff --git a/common/test/test_lint.py b/common/test/test_lint.py
index 5213da0d7..c85503bef 100644
--- a/common/test/test_lint.py
+++ b/common/test/test_lint.py
@@ -9,6 +9,7 @@
PYLINT_AVIALBE = not shutil.which('pylint') is None
PYLINT_REASON = ('Using PyLint is mandatory on TravisCI, on other systems'
'it runs only if `pylint` is available.')
+ON_TRAVIS_PPC64LE = os.environ.get('TRAVIS_ARCH', '') == 'ppc64le'
class MirrorMirrorOnTheWall(unittest.TestCase):
@@ -51,8 +52,10 @@ def test_with_pylint(self):
# Pylint base command
cmd = [
'pylint',
- # Make sure BIT modules can be improted (to detect "no-member")
- '--init-hook=import sys;sys.path.insert(0, "./../qt");',
+ # Make sure BIT modules can be imported (to detect "no-member")
+ '--init-hook=import sys;'
+ 'sys.path.insert(0, "./../qt");'
+ 'sys.path.insert(0, "./../common");',
# Storing results in a pickle file is unnecessary
'--persistent=n',
# autodetec number of parallel jobs
@@ -79,9 +82,26 @@ def test_with_pylint(self):
'E0401', # import-error
'I0021', # useless-suppression
]
+
+ if ON_TRAVIS_PPC64LE:
+ # Because of missing PyQt6 on ppc64le architecture
+ err_codes.remove('I0021')
+ err_codes.remove('E0401')
+
cmd.append('--enable=' + ','.join(err_codes))
# Add py files
cmd.extend(self._collect_py_files())
- subprocess.run(cmd, check=True)
+ r = subprocess.run(
+ cmd,
+ check=False,
+ universal_newlines=True,
+ capture_output=True)
+
+ # Count lines except module headings
+ error_n = len(list(filter(lambda line: not line.startswith('*****'),
+ r.stdout.splitlines())))
+ print(r.stdout)
+
+ self.assertEqual(0, error_n, f'PyLint found {error_n} problems.')
diff --git a/common/test/test_plugin_usercallback.py b/common/test/test_plugin_usercallback.py
index ce2c6a1cf..b0ae9ef0e 100644
--- a/common/test/test_plugin_usercallback.py
+++ b/common/test/test_plugin_usercallback.py
@@ -14,6 +14,7 @@
sys.path.append(str(Path(__file__).parent))
sys.path.append(str(Path(__file__).parent / 'plugins'))
import logger
+import pluginmanager
from config import Config
from snapshots import Snapshots, SID
from usercallbackplugin import UserCallbackPlugin
@@ -131,8 +132,7 @@ def _create_user_callback_file(cls, parent_path):
content = inspect.cleandoc('''
#!/usr/bin/env python3
import sys
- response = sys.argv[1:]
- print(response)
+ print(sys.argv[1:])
''')
callback_fp = parent_path / 'user-callback'
@@ -179,6 +179,8 @@ def _extract_callback_responses(cls, output):
callback_responses = []
for line in response_lines:
+ to_eval = line[line.index("'")+1:line.rindex("'")]
+
callback_responses.append(
eval(line[line.index("'")+1:line.rindex("'")])
)
@@ -252,6 +254,9 @@ def setUp(self):
self.config_fp = self._create_config_file(self.temp_path)
self._create_user_callback_file(self.config_fp.parent)
+ # Reset this instance because it is not isolated between tests.
+ Config.PLUGIN_MANAGER = pluginmanager.PluginManager()
+
def test_local_snapshot(self):
"""User-callback response while doing a local snapshot"""
diff --git a/common/tools.py b/common/tools.py
index 768f52c01..44aeb371a 100644
--- a/common/tools.py
+++ b/common/tools.py
@@ -53,9 +53,9 @@
# because the latter is still not available here in the global
# module code.
if os.getenv('BIT_USE_KEYRING', 'true') == 'true' and os.geteuid() != 0:
- import keyring # pylint: disable=import-error
- from keyring import backend # pylint: disable=import-error
- import keyring.util.platform_ # pylint: disable=import-error
+ import keyring
+ from keyring import backend
+ import keyring.util.platform_
is_keyring_available = True
except Exception as e:
is_keyring_available = False
diff --git a/qt/aboutdlg.py b/qt/aboutdlg.py
index 1602a1571..4e86b8903 100644
--- a/qt/aboutdlg.py
+++ b/qt/aboutdlg.py
@@ -29,7 +29,7 @@
import backintime
-class AboutDlg(QDialog): # pylint: disable=too-few-public-methods
+class AboutDlg(QDialog):
"""The about dialog accessible from the Help menu in the main window."""
def __init__(self, parent=None):
diff --git a/qt/languagedialog.py b/qt/languagedialog.py
index 556c60d6b..3af759411 100644
--- a/qt/languagedialog.py
+++ b/qt/languagedialog.py
@@ -169,7 +169,6 @@ def slot_radio(self, _):
btn = self.sender()
if btn.isChecked():
- # pylint: disable-next=attribute-defined-outside-init
self.language_code = btn.lang_code
@@ -268,7 +267,7 @@ def _fix_size(self):
if self.height() < best.height():
self.resize(best)
- def resizeEvent(self, event): # pylint: disable=invalid-name
+ def resizeEvent(self, event):
"""See `_fixSize()` for details."""
super().resizeEvent(event)
diff --git a/qt/logviewdialog.py b/qt/logviewdialog.py
index 21f5495d7..364d4fcdd 100644
--- a/qt/logviewdialog.py
+++ b/qt/logviewdialog.py
@@ -31,7 +31,6 @@
class LogViewDialog(QDialog):
# Workaround because of *-imports of Qt elements.
# Remove as soon as possible.
- # pylint: disable=undefined-variable
def __init__(self, parent, sid = None, systray = False):
"""
Instantiate a snapshot log file viewer
diff --git a/qt/serviceHelper.py b/qt/serviceHelper.py
index fd9cc6bdc..cb28ad06d 100644
--- a/qt/serviceHelper.py
+++ b/qt/serviceHelper.py
@@ -71,11 +71,13 @@
# "dbus-python" not available for ppc64le architecture
# "dbus.mainloop.pyqt6" not available via PyPi for any architecture
-import dbus # pylint: disable=import-error
-import dbus.service # pylint: disable=import-error
-import dbus.mainloop # pylint: disable=import-error
-import dbus.mainloop.pyqt6 # pylint: disable=import-error
-from dbus.mainloop.pyqt6 import DBusQtMainLoop # pylint: disable=import-error
+import dbus
+import dbus.service
+import dbus.mainloop
+# pylint: disable-next=import-error,useless-suppression
+import dbus.mainloop.pyqt6
+# pylint: disable-next=import-error,useless-suppression
+from dbus.mainloop.pyqt6 import DBusQtMainLoop
from PyQt6.QtCore import QCoreApplication
UDEV_RULES_PATH = '/etc/udev/rules.d/99-backintime-%s.rules'
diff --git a/qt/test/test_lint.py b/qt/test/test_lint.py
index 781f4a7f2..c85503bef 100644
--- a/qt/test/test_lint.py
+++ b/qt/test/test_lint.py
@@ -9,6 +9,7 @@
PYLINT_AVIALBE = not shutil.which('pylint') is None
PYLINT_REASON = ('Using PyLint is mandatory on TravisCI, on other systems'
'it runs only if `pylint` is available.')
+ON_TRAVIS_PPC64LE = os.environ.get('TRAVIS_ARCH', '') == 'ppc64le'
class MirrorMirrorOnTheWall(unittest.TestCase):
@@ -51,8 +52,10 @@ def test_with_pylint(self):
# Pylint base command
cmd = [
'pylint',
- # Workaround
- '--init-hook=import sys;sys.path.insert(0, "./../common");',
+ # Make sure BIT modules can be imported (to detect "no-member")
+ '--init-hook=import sys;'
+ 'sys.path.insert(0, "./../qt");'
+ 'sys.path.insert(0, "./../common");',
# Storing results in a pickle file is unnecessary
'--persistent=n',
# autodetec number of parallel jobs
@@ -79,9 +82,26 @@ def test_with_pylint(self):
'E0401', # import-error
'I0021', # useless-suppression
]
+
+ if ON_TRAVIS_PPC64LE:
+ # Because of missing PyQt6 on ppc64le architecture
+ err_codes.remove('I0021')
+ err_codes.remove('E0401')
+
cmd.append('--enable=' + ','.join(err_codes))
# Add py files
cmd.extend(self._collect_py_files())
- subprocess.run(cmd, check=True)
+ r = subprocess.run(
+ cmd,
+ check=False,
+ universal_newlines=True,
+ capture_output=True)
+
+ # Count lines except module headings
+ error_n = len(list(filter(lambda line: not line.startswith('*****'),
+ r.stdout.splitlines())))
+ print(r.stdout)
+
+ self.assertEqual(0, error_n, f'PyLint found {error_n} problems.')