From d7f0ec0999b75ebb1eaa2d397d395408a3409489 Mon Sep 17 00:00:00 2001 From: Kosta Vukicevic Date: Mon, 8 Apr 2024 12:27:15 +0200 Subject: [PATCH 01/14] Added checkbox to allow debugging to `settings>general>schedule` in qt and the necessary code to common to make it work. Fixes #1616 --- common/config.py | 9 ++++++++- qt/settingsdialog.py | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/common/config.py b/common/config.py index f7776784a..24b1ea8ab 100644 --- a/common/config.py +++ b/common/config.py @@ -958,6 +958,13 @@ def scheduleMode(self, profile_id = None): def setScheduleMode(self, value, profile_id = None): self.setProfileIntValue('schedule.mode', value, profile_id) + def debugSchedule(self, profile_id = None): + #?Enable debug output to system log for schedule mode. + return self.profileBoolValue('schedule.debug', False, profile_id) + + def setDebugSchedule(self, value, profile_id = None): + self.setProfileBoolValue('schedule.debug', value, profile_id) + def scheduleTime(self, profile_id = None): #?Position-coded number with the format "hhmm" to specify the hour #?and minute the cronjob should start (eg. 2015 means a quarter @@ -1756,7 +1763,7 @@ def cronCmd(self, profile_id): cmd += '--profile-id %s ' % profile_id if not self._LOCAL_CONFIG_PATH is self._DEFAULT_CONFIG_PATH: cmd += '--config %s ' % self._LOCAL_CONFIG_PATH - if logger.DEBUG: + if logger.DEBUG or self.debugSchedule(profile_id): cmd += '--debug ' cmd += 'backup-job' if self.redirectStdoutInCron(profile_id): diff --git a/qt/settingsdialog.py b/qt/settingsdialog.py index 4f78b9332..f8ea3e0b0 100644 --- a/qt/settingsdialog.py +++ b/qt/settingsdialog.py @@ -482,6 +482,11 @@ def __init__(self, parent): self.comboSchedule.currentIndexChanged.connect(self.scheduleChanged) + self.cbDebugSchedule = QCheckBox(self) + self.cbDebugSchedule.setText(_('Enable logging of debug messages')) + self.cbDebugSchedule.setToolTip(('Write debug-level messages into the system log. Warning: Use only to diagnose problems since it creates a lot of output.')) + glayout.addWidget(self.cbDebugSchedule, 8, 0) + # layout.addStretch() scrollArea.setWidget(layoutWidget) @@ -1348,6 +1353,8 @@ def updateProfile(self): self.config.scheduleRepeatedUnit()) self.updateSchedule(self.config.scheduleMode()) + self.cbDebugSchedule.setChecked(self.config.debugSchedule()) + # TAB: Include self.listInclude.clear() @@ -1584,6 +1591,8 @@ def saveProfile(self): self.comboScheduleRepeatedUnit.itemData( self.comboScheduleRepeatedUnit.currentIndex())) + self.config.setDebugSchedule(self.cbDebugSchedule.isChecked()) + # auto-remove self.config.setRemoveOldSnapshots( self.cbRemoveOlder.isChecked(), From 71387c831e56c7b73c73dbce2ae43ed8679826c4 Mon Sep 17 00:00:00 2001 From: Kosta Vukicevic Date: Wed, 10 Apr 2024 15:52:16 +0200 Subject: [PATCH 02/14] Updated changelog and added tests Added the change to CHANGES Added two tests (`test_crontab_contains_debug` and `test_crontab_without_debug`) to `test_backup.py` --- CHANGES | 1 + common/test/test_backup.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/CHANGES b/CHANGES index aadc27e2e..f2d3f0220 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Back In Time Version 1.4.4-dev (development of upcoming release) +* Feature: Debug output for scheduled jobs (--debug in crontab) * Fix bug: Open symlinked folders in file view (#1476) * Fix bug: Respect dark mode using color roles (#1601) * Fix: "Highly recommended" exclusion pattern in "Manage Profile" dialog's "Exclude" tab show missings only (#1620) diff --git a/common/test/test_backup.py b/common/test/test_backup.py index 16e720dd3..31dd05902 100644 --- a/common/test/test_backup.py +++ b/common/test/test_backup.py @@ -255,3 +255,37 @@ def test_takeSnapshot_exception_cleanup(self, takeSnapshot, sleep): self.sn.backup() self.assertFalse(new.saveToContinue) self.assertTrue(new.failed) + + def test_crontab_contains_debug(self, sleep): + # create config object with default values + conf = config.Config('config') + + # assert start conditions: NO DEBUGGING + self.assertFalse(conf.debugSchedule()) + self.assertFalse(logger.DEBUG) + + # the "system under test" (sut) + sut = conf.cronCmd(profile_id='1') + # assert that the command string does not contain the debug flag + self.assertNotIn('--debug', sut) + + # now enable debugging + conf.setDebugSchedule(True) + + # assert that debugging is enabled + self.assertTrue(conf.debugSchedule()) + # again the "system under test" (sut) + sut = conf.cronCmd(profile_id='1') + # assert that the command string contains the debug flag + self.assertIn('--debug', sut) + + def test_crontab_without_debug(self, sleep): + conf = config.Config('config') + + conf.setDebugSchedule(False) + + self.assertFalse(conf.debugSchedule()) + self.assertFalse(logger.DEBUG) + + sut = conf.cronCmd(profile_id='1') + self.assertNotIn('--debug', sut) \ No newline at end of file From 086379408dfd04aa60cc687339ed25b5f12cab5e Mon Sep 17 00:00:00 2001 From: Kosta Vukicevic Date: Wed, 10 Apr 2024 16:15:15 +0200 Subject: [PATCH 03/14] Set `logger.DEBUG` to `false` It make `make test-v` exit with non zero code --- common/test/test_backup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/test/test_backup.py b/common/test/test_backup.py index 31dd05902..68807dc46 100644 --- a/common/test/test_backup.py +++ b/common/test/test_backup.py @@ -259,7 +259,8 @@ def test_takeSnapshot_exception_cleanup(self, takeSnapshot, sleep): def test_crontab_contains_debug(self, sleep): # create config object with default values conf = config.Config('config') - + logger.DEBUG = False + # assert start conditions: NO DEBUGGING self.assertFalse(conf.debugSchedule()) self.assertFalse(logger.DEBUG) @@ -283,6 +284,7 @@ def test_crontab_without_debug(self, sleep): conf = config.Config('config') conf.setDebugSchedule(False) + logger.DEBUG = False self.assertFalse(conf.debugSchedule()) self.assertFalse(logger.DEBUG) From 6653f50bbac2e43f2079e99eddb8e1ce59fd34e3 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 11:15:02 +0200 Subject: [PATCH 04/14] moved tests and some more --- common/config.py | 37 +++++++++++- common/test/test_backup.py | 43 +++----------- common/test/test_crontab.py | 113 ++++++++++++++++++++++++++++++++++++ common/tools.py | 44 +++++++++----- 4 files changed, 184 insertions(+), 53 deletions(-) create mode 100644 common/test/test_crontab.py diff --git a/common/config.py b/common/config.py index 24b1ea8ab..59036c5e4 100644 --- a/common/config.py +++ b/common/config.py @@ -1755,28 +1755,59 @@ def cronLine(self, profile_id): return cron_line def cronCmd(self, profile_id): - if not tools.checkCommand('backintime'): - logger.error("Command 'backintime' not found", self) - return + """Generates the command used in the crontab file based on the settings + for the current profile. + + Returns: + str: The crontab line. + """ + + # buhtz (2024-04): IMHO meaningless in productive environments. + # if not tools.checkCommand('backintime'): + # logger.error("Command 'backintime' not found", self) + # return + + # Get full path of the Back In Time binary cmd = tools.which('backintime') + ' ' + + # The "--profile-id" argument is used only for profiles different from + # frist profile if profile_id != '1': cmd += '--profile-id %s ' % profile_id + + # User definied path to config file if not self._LOCAL_CONFIG_PATH is self._DEFAULT_CONFIG_PATH: cmd += '--config %s ' % self._LOCAL_CONFIG_PATH + + # Enable debug output if logger.DEBUG or self.debugSchedule(profile_id): cmd += '--debug ' + + # command cmd += 'backup-job' + + # Redirect stdout to nirvana if self.redirectStdoutInCron(profile_id): cmd += ' >/dev/null' + + # Redirect stderr ... if self.redirectStderrInCron(profile_id): + if self.redirectStdoutInCron(profile_id): + # ... to stdout cmd += ' 2>&1' else: + # ... to nirvana cmd += ' 2>/dev/null' + + # IO priority: low (-n7) in "best effort" class (-c2) if self.ioniceOnCron(profile_id) and tools.checkCommand('ionice'): cmd = tools.which('ionice') + ' -c2 -n7 ' + cmd + + # CPU priority: very low if self.niceOnCron(profile_id) and tools.checkCommand('nice'): cmd = tools.which('nice') + ' -n19 ' + cmd + return cmd diff --git a/common/test/test_backup.py b/common/test/test_backup.py index 68807dc46..a55cb64c9 100644 --- a/common/test/test_backup.py +++ b/common/test/test_backup.py @@ -256,38 +256,11 @@ def test_takeSnapshot_exception_cleanup(self, takeSnapshot, sleep): self.assertFalse(new.saveToContinue) self.assertTrue(new.failed) - def test_crontab_contains_debug(self, sleep): - # create config object with default values - conf = config.Config('config') - logger.DEBUG = False - - # assert start conditions: NO DEBUGGING - self.assertFalse(conf.debugSchedule()) - self.assertFalse(logger.DEBUG) - - # the "system under test" (sut) - sut = conf.cronCmd(profile_id='1') - # assert that the command string does not contain the debug flag - self.assertNotIn('--debug', sut) - - # now enable debugging - conf.setDebugSchedule(True) - - # assert that debugging is enabled - self.assertTrue(conf.debugSchedule()) - # again the "system under test" (sut) - sut = conf.cronCmd(profile_id='1') - # assert that the command string contains the debug flag - self.assertIn('--debug', sut) - - def test_crontab_without_debug(self, sleep): - conf = config.Config('config') - - conf.setDebugSchedule(False) - logger.DEBUG = False - - self.assertFalse(conf.debugSchedule()) - self.assertFalse(logger.DEBUG) - - sut = conf.cronCmd(profile_id='1') - self.assertNotIn('--debug', sut) \ No newline at end of file + def test_foobar(self, sleep): + c = config.Config('config') + import tools + w = tools.which('backintime') + print(f'test_foobar() :: {w=}') + return + cmd = c.cronCmd(1) + print(f'xxxxx {cmd=}') diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py new file mode 100644 index 000000000..118d4e472 --- /dev/null +++ b/common/test/test_crontab.py @@ -0,0 +1,113 @@ +# Back In Time +# Copyright (C) 2024 Kosta Vukicevic, Christian Buhtz +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation,Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import unittest +import pyfakefs.fake_filesystem_unittest as pyfakefs_ut +import sys +import os +import tempfile +import inspect +from pathlib import Path +from unittest import mock + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import backintime +import config +import snapshots +import tools +import logger + + +class CrontabDebug(pyfakefs_ut.TestCase): + """Debug behavior when scheduled via crontab""" + def setUp(self): + """Setup a fake filesystem with a config file.""" + self.setUpPyfakefs(allow_root_user=False) + + # cleanup() happens automatically + self._temp_dir = tempfile.TemporaryDirectory(prefix='bit.') + # Workaround: tempfile and pathlib not compatible yet + self.temp_path = Path(self._temp_dir.name) + + self.config_fp = self._create_config_file(parent_path=self.temp_path) + + logger.DEBUG = False + + def _create_config_file(cls, parent_path): + """Minimal config file""" + cfg_content = inspect.cleandoc(''' + config.version=6 + profile1.snapshots.include.1.type=0 + profile1.snapshots.include.1.value=rootpath/source + profile1.snapshots.include.size=1 + profile1.snapshots.no_on_battery=false + profile1.snapshots.notify.enabled=true + profile1.snapshots.path=rootpath/destination + profile1.snapshots.path.host=test-host + profile1.snapshots.path.profile=1 + profile1.snapshots.path.user=test-user + profile1.snapshots.preserve_acl=false + profile1.snapshots.preserve_xattr=false + profile1.snapshots.remove_old_snapshots.enabled=true + profile1.snapshots.remove_old_snapshots.unit=80 + profile1.snapshots.remove_old_snapshots.value=10 + profile1.snapshots.rsync_options.enabled=false + profile1.snapshots.rsync_options.value= + profiles.version=1 + ''') + + # cfg_content = cfg_content.format( + # rootpath=parent_path, + # source=cls.NAME_SOURCE, + # destination=cls.NAME_DESTINATION + # ) + + # config file location + config_fp = parent_path / 'config_path' / 'config' + config_fp.parent.mkdir() + config_fp.write_text(cfg_content, 'utf-8') + + return config_fp + + @mock.patch('tools.which', return_value='backintime') + def test_crontab_contains_debug(self, mock_which): + """ + About mock_which: A workaround because the function gives + false-negative when using a fake filesystem. + """ + conf = config.Config(str(self.config_fp)) + + # set and assert start conditions + conf.setDebugSchedule(True) + self.assertTrue(conf.debugSchedule()) + + sut = conf.cronCmd(profile_id='1') + self.assertIn('--debug', sut) + + @mock.patch('tools.which', return_value='backintime') + def test_crontab_without_debug(self, mock_which): + """No debug output in crontab line. + + About mock_which: See test_crontab_with_debug(). + """ + conf = config.Config(str(self.config_fp)) + + # set and assert start conditions + conf.setDebugSchedule(False) + self.assertFalse(conf.debugSchedule()) + + sut = conf.cronCmd(profile_id='1') + self.assertNotIn('--debug', sut) diff --git a/common/tools.py b/common/tools.py index 44aeb371a..a33ff8563 100644 --- a/common/tools.py +++ b/common/tools.py @@ -353,11 +353,15 @@ def registerBackintimePath(*path): sys.path.insert(0, path) def runningFromSource(): - """ - Check if BackInTime is running from source (without installing). + """Check if BackInTime is running from source (without installing). + + Dev notes by buhtz (2024-04): This function is dangerous and will give a + false-negative in fake filesystems (e.g. PyFakeFS). The function should + not exist. Beside unit tests it is used only two times. Remove it until + migration to pyproject.toml based project packaging (#1575). Returns: - bool: ``True`` if BackInTime is running from source + bool: ``True`` if BackInTime is running from source. """ return os.path.isfile(backintimePath('common', 'backintime')) @@ -484,15 +488,15 @@ def readFileLines(path, default = None): return ret_val + def checkCommand(cmd): - """ - Check if command ``cmd`` is a file in 'PATH' environ. + """Check if command ``cmd`` is a file in 'PATH' environment. Args: - cmd (str): command + cmd (str): The command. Returns: - bool: ``True`` if command ``cmd`` is in 'PATH' environ + bool: ``True`` if ``cmd`` is in 'PATH' environment otherwise ``False``. """ cmd = cmd.strip() @@ -501,29 +505,39 @@ def checkCommand(cmd): if os.path.isfile(cmd): return True - return not which(cmd) is None + + return which(cmd) is not None + def which(cmd): - """ - Get the fullpath of executable command ``cmd``. Works like - command-line 'which' command. + """Get the fullpath of executable command ``cmd``. + + Works like command-line 'which' command. + + Dev note by buhtz (2024-04): Give false-negative results in fake + filesystems. Quit often use in the whole code base. But not sure why + can we replace it with "which" from shell? Args: - cmd (str): command + cmd (str): The command. Returns: - str: fullpath of command ``cmd`` or ``None`` if command is - not available + str: Fullpath of command ``cmd`` or ``None`` if command is not + available. """ pathenv = os.getenv('PATH', '') - path = pathenv.split(":") + path = pathenv.split(':') common = backintimePath('common') + if runningFromSource() and common not in path: path.insert(0, common) + for directory in path: fullpath = os.path.join(directory, cmd) + if os.path.isfile(fullpath) and os.access(fullpath, os.X_OK): return fullpath + return None def makeDirs(path): From 85a2e4f95178c99ca491e8c7da957e26a5443154 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 11:19:44 +0200 Subject: [PATCH 05/14] research about deboug output --- common/test/test_crontab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py index 118d4e472..75123a45d 100644 --- a/common/test/test_crontab.py +++ b/common/test/test_crontab.py @@ -44,7 +44,7 @@ def setUp(self): self.config_fp = self._create_config_file(parent_path=self.temp_path) - logger.DEBUG = False + # logger.DEBUG = False def _create_config_file(cls, parent_path): """Minimal config file""" From ea99635765b7d15e0c4cd8d3b9da0d8e46bac643 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 11:23:38 +0200 Subject: [PATCH 06/14] research about deboug output TRUE --- common/test/test_crontab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py index 75123a45d..d699c6152 100644 --- a/common/test/test_crontab.py +++ b/common/test/test_crontab.py @@ -44,7 +44,7 @@ def setUp(self): self.config_fp = self._create_config_file(parent_path=self.temp_path) - # logger.DEBUG = False + logger.DEBUG = True def _create_config_file(cls, parent_path): """Minimal config file""" From 6b338276635119c06d02d02743adccb7ac2421be Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 11:45:45 +0200 Subject: [PATCH 07/14] fixed the DEBUG output problem --- CHANGES | 3 ++- common/config.py | 2 +- common/test/test_crontab.py | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index f2d3f0220..dfaf6b783 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Back In Time Version 1.4.4-dev (development of upcoming release) -* Feature: Debug output for scheduled jobs (--debug in crontab) +* Breaking Change: Argument "--debug" in crontab not used anymore when GUI runs in debug mode. +* Feature: Profile setting to activate debug output for scheduled jobs (--debug in crontab) * Fix bug: Open symlinked folders in file view (#1476) * Fix bug: Respect dark mode using color roles (#1601) * Fix: "Highly recommended" exclusion pattern in "Manage Profile" dialog's "Exclude" tab show missings only (#1620) diff --git a/common/config.py b/common/config.py index 59036c5e4..14dafe326 100644 --- a/common/config.py +++ b/common/config.py @@ -1780,7 +1780,7 @@ def cronCmd(self, profile_id): cmd += '--config %s ' % self._LOCAL_CONFIG_PATH # Enable debug output - if logger.DEBUG or self.debugSchedule(profile_id): + if self.debugSchedule(profile_id): cmd += '--debug ' # command diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py index d699c6152..662df265c 100644 --- a/common/test/test_crontab.py +++ b/common/test/test_crontab.py @@ -44,8 +44,6 @@ def setUp(self): self.config_fp = self._create_config_file(parent_path=self.temp_path) - logger.DEBUG = True - def _create_config_file(cls, parent_path): """Minimal config file""" cfg_content = inspect.cleandoc(''' From 53981218bcf059aa5fc0d2b5443133461f13b7d2 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 12:50:39 +0200 Subject: [PATCH 08/14] multiple minor mods --- CHANGES | 2 +- FAQ.md | 2 +- common/config.py | 4 ++-- common/snapshots.py | 6 +++--- common/test/test_backup.py | 9 --------- common/test/test_crontab.py | 7 ------- common/test/test_diagnostics.py | 2 +- common/test/test_lint.py | 35 +++++++++++++++++++++++++++++++-- common/test/test_tools.py | 2 +- qt/settingsdialog.py | 7 +++++-- qt/test/test_lint.py | 32 ++++++++++++++++++++++++++++-- 11 files changed, 77 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index dfaf6b783..44753a721 100644 --- a/CHANGES +++ b/CHANGES @@ -5,7 +5,7 @@ Version 1.4.4-dev (development of upcoming release) * Feature: Profile setting to activate debug output for scheduled jobs (--debug in crontab) * Fix bug: Open symlinked folders in file view (#1476) * Fix bug: Respect dark mode using color roles (#1601) -* Fix: "Highly recommended" exclusion pattern in "Manage Profile" dialog's "Exclude" tab show missings only (#1620) +* Fix: "Highly recommended" exclusion pattern in "Manage Profile" dialog's "Exclude" tab show missing only (#1620) * Build: Activate PyLint error E0401 (import-error) * Dependency: Migration to PyQt6 * Build: PyLint unit test is skipped if PyLint isn't installed, but will always run on TravisCI (#1634) diff --git a/FAQ.md b/FAQ.md index f9c80cd20..0607f1a78 100644 --- a/FAQ.md +++ b/FAQ.md @@ -455,7 +455,7 @@ Otherwise, kill the process. After that look into the folder For more details see the developer documentation: [Usage of control files (locks, flocks, logs and others)](common/doc-dev/4_Control_files_usage_(locks_flocks_logs_and_others).md) ### Switching to dark or light mode in the desktop environment is ignored by BIT -After restart _Back In Time_ it should addapt to the desktops current used +After restart _Back In Time_ it should adapt to the desktops current used color theme. It happens because Qt does not detect theme modifications out of the diff --git a/common/config.py b/common/config.py index 14dafe326..15e6c90fb 100644 --- a/common/config.py +++ b/common/config.py @@ -1771,11 +1771,11 @@ def cronCmd(self, profile_id): cmd = tools.which('backintime') + ' ' # The "--profile-id" argument is used only for profiles different from - # frist profile + # first profile if profile_id != '1': cmd += '--profile-id %s ' % profile_id - # User definied path to config file + # User defined path to config file if not self._LOCAL_CONFIG_PATH is self._DEFAULT_CONFIG_PATH: cmd += '--config %s ' % self._LOCAL_CONFIG_PATH diff --git a/common/snapshots.py b/common/snapshots.py index 4a8c80c44..8ec800056 100644 --- a/common/snapshots.py +++ b/common/snapshots.py @@ -53,7 +53,7 @@ class Snapshots: the class `SID` which represents a snapshot in the "data layer". BUHTZ 2024-02-23: Not sure but it seems to be one concret snapshot and - not a collection of snapshots. In this case the class name is missleading + not a collection of snapshots. In this case the class name is misleading because it is in plural form. Args: @@ -104,7 +104,7 @@ def takeSnapshotMessage(self): Dev note (buhtz): Too many try..excepts in here. """ - # Dev note (buhtz): Not sure what happens here or why this is usefull. + # Dev note (buhtz): Not sure what happens here or why this is useful. wait = datetime.datetime.now() - datetime.timedelta(seconds=5) if self.lastBusyCheck < wait: @@ -173,7 +173,7 @@ def setTakeSnapshotMessage(self, type_id, message, timeout=-1): message_fn = self.config.takeSnapshotMessageFile() try: - # Write message to file (and overwrites the previos one) + # Write message to file (and overwrites the previous one) with open(message_fn, 'wt') as f: f.write(str(type_id) + '\n' + message) diff --git a/common/test/test_backup.py b/common/test/test_backup.py index a55cb64c9..16e720dd3 100644 --- a/common/test/test_backup.py +++ b/common/test/test_backup.py @@ -255,12 +255,3 @@ def test_takeSnapshot_exception_cleanup(self, takeSnapshot, sleep): self.sn.backup() self.assertFalse(new.saveToContinue) self.assertTrue(new.failed) - - def test_foobar(self, sleep): - c = config.Config('config') - import tools - w = tools.which('backintime') - print(f'test_foobar() :: {w=}') - return - cmd = c.cronCmd(1) - print(f'xxxxx {cmd=}') diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py index 662df265c..6c97c8dd8 100644 --- a/common/test/test_crontab.py +++ b/common/test/test_crontab.py @@ -22,7 +22,6 @@ import inspect from pathlib import Path from unittest import mock - sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import backintime import config @@ -67,12 +66,6 @@ def _create_config_file(cls, parent_path): profiles.version=1 ''') - # cfg_content = cfg_content.format( - # rootpath=parent_path, - # source=cls.NAME_SOURCE, - # destination=cls.NAME_DESTINATION - # ) - # config file location config_fp = parent_path / 'config_path' / 'config' config_fp.parent.mkdir() diff --git a/common/test/test_diagnostics.py b/common/test/test_diagnostics.py index e611d1664..4168f12fa 100644 --- a/common/test/test_diagnostics.py +++ b/common/test/test_diagnostics.py @@ -28,7 +28,7 @@ def test_content_minimal(self): self.assertCountEqual(sut['host-setup'].keys(), ['OS']) def test_some_content(self): - """Some containted elements""" + """Some contained elements""" result = diagnostics.collect_diagnostics() # 1st level keys diff --git a/common/test/test_lint.py b/common/test/test_lint.py index c85503bef..526379de7 100644 --- a/common/test/test_lint.py +++ b/common/test/test_lint.py @@ -3,6 +3,7 @@ import pathlib import subprocess import shutil +import inspect from typing import Iterable ON_TRAVIS = os.environ.get('TRAVIS', '') == 'true' @@ -65,22 +66,49 @@ def test_with_pylint(self): # Deactivate all checks by default '--disable=all', # prevent false-positive no-module-member errors - '--extension-pkg-whitelist=PyQt6,PyQt6.QtCore', + '--extension-pkg-allow-list=PyQt6,PyQt6.QtCore', # Because of globally installed GNU gettext functions '--additional-builtins=_,ngettext', # PEP8 conform line length (see PyLint Issue #3078) '--max-line-length=79', # Whitelist variable names '--good-names=idx,fp', + # '--reports=yes', ] # Explicit activate checks err_codes = [ + 'E0401', # import-error 'E0602', # undefined-variable 'E1101', # no-member + 'W1301', # unused-format-string-key 'W1401', # anomalous-backslash-in-string (invalid escape sequence) - 'E0401', # import-error 'I0021', # useless-suppression + + # Enable asap. This list is selection of existing (not all!) + # problems currently exiting in the BIT code base. Quit easy to fix + # because there count is low. + # 'C0303', # trailing-whitespace + # 'C0305', # trailing-newlines + # 'C0324', # superfluous-parens + # 'C0410', # multiple-imports + # 'E0213', # no-self-argument + # 'R0201', # no-self-use + # 'R0202', # no-classmethod-decorator + # 'R0203', # no-staticmethod-decorator + # 'R0801', # duplicate-code + # 'W0123', # eval-used + # 'W0237', # arguments-renamed + # 'W0221', # arguments-differ + # 'W0311', # bad-indentation + # 'W0404', # reimported + # 'W4902', # deprecated-method + # 'W4904', # deprecated-class + # 'W0603', # global-statement + # 'W0614', # unused-wildcard-import + # 'W0611', # unused-import + # 'W0612', # unused-variable + # 'W0707', # raise-missing-from ] if ON_TRAVIS_PPC64LE: @@ -105,3 +133,6 @@ def test_with_pylint(self): print(r.stdout) self.assertEqual(0, error_n, f'PyLint found {error_n} problems.') + + # any other errors? + self.assertEqual(r.stderr, '') diff --git a/common/test/test_tools.py b/common/test/test_tools.py index 0f3662a51..5792269eb 100644 --- a/common/test/test_tools.py +++ b/common/test/test_tools.py @@ -1030,7 +1030,7 @@ def setUp(self): self.setUpPyfakefs(allow_root_user=False) def test_git_repo_info_none(self): - """Acutally not a git repo""" + """Actually not a git repo""" self.assertEqual(tools.get_git_repository_info(), None) diff --git a/qt/settingsdialog.py b/qt/settingsdialog.py index f8ea3e0b0..02030cb12 100644 --- a/qt/settingsdialog.py +++ b/qt/settingsdialog.py @@ -484,7 +484,10 @@ def __init__(self, parent): self.cbDebugSchedule = QCheckBox(self) self.cbDebugSchedule.setText(_('Enable logging of debug messages')) - self.cbDebugSchedule.setToolTip(('Write debug-level messages into the system log. Warning: Use only to diagnose problems since it creates a lot of output.')) + self.cbDebugSchedule.setToolTip(_( + 'Writes debug-level messages into the system log. Caution: Only ' + 'use this for diagnosis, as it generates a large amount of output.' + )) glayout.addWidget(self.cbDebugSchedule, 8, 0) # @@ -2138,7 +2141,7 @@ def _formatExcludeItem(self, item): item.setIcon(0, self.icon.DEFAULT_EXCLUDE) return - # Icon: user definied + # Icon: user defined item.setIcon(0, self.icon.EXCLUDE) diff --git a/qt/test/test_lint.py b/qt/test/test_lint.py index c85503bef..22e6eab06 100644 --- a/qt/test/test_lint.py +++ b/qt/test/test_lint.py @@ -65,22 +65,50 @@ def test_with_pylint(self): # Deactivate all checks by default '--disable=all', # prevent false-positive no-module-member errors - '--extension-pkg-whitelist=PyQt6,PyQt6.QtCore', + '--extension-pkg-allow-list=PyQt6,PyQt6.QtCore', # Because of globally installed GNU gettext functions '--additional-builtins=_,ngettext', # PEP8 conform line length (see PyLint Issue #3078) '--max-line-length=79', # Whitelist variable names '--good-names=idx,fp', + # '--reports=yes', ] # Explicit activate checks err_codes = [ + 'E0401', # import-error 'E0602', # undefined-variable 'E1101', # no-member + 'W0611', # unused-import + 'W1301', # unused-format-string-key 'W1401', # anomalous-backslash-in-string (invalid escape sequence) - 'E0401', # import-error 'I0021', # useless-suppression + + # Enable asap. This list is selection of existing (not all!) + # problems currently exiting in the BIT code base. Quit easy to fix + # because there count is low. + # 'C0303', # trailing-whitespace + # 'C0305', # trailing-newlines + # 'C0324', # superfluous-parens + # 'C0410', # multiple-imports + # 'E0213', # no-self-argument + # 'R0201', # no-self-use + # 'R0202', # no-classmethod-decorator + # 'R0203', # no-staticmethod-decorator + # 'R0801', # duplicate-code + # 'W0123', # eval-used + # 'W0237', # arguments-renamed + # 'W0221', # arguments-differ + # 'W0311', # bad-indentation + # 'W0404', # reimported + # 'W4902', # deprecated-method + # 'W4904', # deprecated-class + # 'W0603', # global-statement + # 'W0614', # unused-wildcard-import + # 'W0611', # unused-import + # 'W0612', # unused-variable + # 'W0707', # raise-missing-from ] if ON_TRAVIS_PPC64LE: From a0f647f68098fcbb2a56ef4f6c57db0f4e879277 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 12:54:33 +0200 Subject: [PATCH 09/14] fixed pylint errors --- qt/app.py | 4 ---- qt/plugins/systrayiconplugin.py | 4 +--- qt/qttools_path.py | 1 - qt/restoredialog.py | 1 - 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/qt/app.py b/qt/app.py index 07a3831d8..09d691140 100644 --- a/qt/app.py +++ b/qt/app.py @@ -52,7 +52,6 @@ QShortcut, QDesktopServices, QPalette, - QColor, QIcon, QFileSystemModel) from PyQt6.QtWidgets import (QWidget, @@ -69,7 +68,6 @@ QAbstractItemView, QStyledItemDelegate, QVBoxLayout, - QHBoxLayout, QStackedLayout, QSplitter, QGroupBox, @@ -79,7 +77,6 @@ QMessageBox, QInputDialog, QDialog, - QDialogButtonBox, QApplication, ) from PyQt6.QtCore import (Qt, @@ -92,7 +89,6 @@ QEvent, QSortFilterProxyModel, QDir, - QSize, QUrl, pyqtRemoveInputHook, ) diff --git a/qt/plugins/systrayiconplugin.py b/qt/plugins/systrayiconplugin.py index b6aeff675..e01a78e95 100644 --- a/qt/plugins/systrayiconplugin.py +++ b/qt/plugins/systrayiconplugin.py @@ -33,13 +33,11 @@ import pluginmanager import tools import logger -import time import gettext -import _thread import subprocess -_=gettext.gettext +_ = gettext.gettext if not os.getenv('DISPLAY', ''): diff --git a/qt/qttools_path.py b/qt/qttools_path.py index fd6b47641..1dd59eea2 100644 --- a/qt/qttools_path.py +++ b/qt/qttools_path.py @@ -25,7 +25,6 @@ """ import os import sys -import gettext def backintimePath(*path): return os.path.abspath(os.path.join(__file__, os.pardir, os.pardir, *path)) diff --git a/qt/restoredialog.py b/qt/restoredialog.py index 383a5df76..597ea56ba 100644 --- a/qt/restoredialog.py +++ b/qt/restoredialog.py @@ -23,7 +23,6 @@ from PyQt6.QtCore import * import tools -import qttools class RestoreDialog(QDialog): From 1071145b09f489158230acf2a67c69348dd4e676 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Thu, 11 Apr 2024 13:13:40 +0200 Subject: [PATCH 10/14] ignore 31 unused-import errors --- common/test/test_lint.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/test/test_lint.py b/common/test/test_lint.py index 526379de7..4e4524ad0 100644 --- a/common/test/test_lint.py +++ b/common/test/test_lint.py @@ -3,7 +3,6 @@ import pathlib import subprocess import shutil -import inspect from typing import Iterable ON_TRAVIS = os.environ.get('TRAVIS', '') == 'true' @@ -81,6 +80,7 @@ def test_with_pylint(self): 'E0401', # import-error 'E0602', # undefined-variable 'E1101', # no-member + # 'W0611', # unused-import 'W1301', # unused-format-string-key 'W1401', # anomalous-backslash-in-string (invalid escape sequence) 'I0021', # useless-suppression @@ -106,7 +106,6 @@ def test_with_pylint(self): # 'W4904', # deprecated-class # 'W0603', # global-statement # 'W0614', # unused-wildcard-import - # 'W0611', # unused-import # 'W0612', # unused-variable # 'W0707', # raise-missing-from ] From 411baa22c9706e90562f5f783db1f6b64b1013bb Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Mon, 29 Apr 2024 12:19:14 +0200 Subject: [PATCH 11/14] fix pylint err --- qt/settingsdialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qt/settingsdialog.py b/qt/settingsdialog.py index 6627702f2..902a85c51 100644 --- a/qt/settingsdialog.py +++ b/qt/settingsdialog.py @@ -22,7 +22,6 @@ import copy import re import getpass -import textwrap from PyQt6.QtGui import (QIcon, QFont, QPalette, From 38ebdac206eb4e7320d1f9196d7cb7b9ca492500 Mon Sep 17 00:00:00 2001 From: aryoda <11374410+aryoda@users.noreply.github.com> Date: Sat, 4 May 2024 00:39:38 +0200 Subject: [PATCH 12/14] Refactoring for consistent config names. Improved tooltip. --- common/config.py | 6 +++--- common/test/test_crontab.py | 8 ++++---- qt/settingsdialog.py | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/common/config.py b/common/config.py index 2fdfdd7a8..bd216d674 100644 --- a/common/config.py +++ b/common/config.py @@ -989,11 +989,11 @@ def scheduleMode(self, profile_id = None): def setScheduleMode(self, value, profile_id = None): self.setProfileIntValue('schedule.mode', value, profile_id) - def debugSchedule(self, profile_id = None): + def scheduleDebug(self, profile_id = None): #?Enable debug output to system log for schedule mode. return self.profileBoolValue('schedule.debug', False, profile_id) - def setDebugSchedule(self, value, profile_id = None): + def setScheduleDebug(self, value, profile_id = None): self.setProfileBoolValue('schedule.debug', value, profile_id) def scheduleTime(self, profile_id = None): @@ -1819,7 +1819,7 @@ def cronCmd(self, profile_id): cmd += '--config %s ' % self._LOCAL_CONFIG_PATH # Enable debug output - if self.debugSchedule(profile_id): + if self.scheduleDebug(profile_id): cmd += '--debug ' # command diff --git a/common/test/test_crontab.py b/common/test/test_crontab.py index 6c97c8dd8..9c8f72482 100644 --- a/common/test/test_crontab.py +++ b/common/test/test_crontab.py @@ -82,8 +82,8 @@ def test_crontab_contains_debug(self, mock_which): conf = config.Config(str(self.config_fp)) # set and assert start conditions - conf.setDebugSchedule(True) - self.assertTrue(conf.debugSchedule()) + conf.setScheduleDebug(True) + self.assertTrue(conf.scheduleDebug()) sut = conf.cronCmd(profile_id='1') self.assertIn('--debug', sut) @@ -97,8 +97,8 @@ def test_crontab_without_debug(self, mock_which): conf = config.Config(str(self.config_fp)) # set and assert start conditions - conf.setDebugSchedule(False) - self.assertFalse(conf.debugSchedule()) + conf.setScheduleDebug(False) + self.assertFalse(conf.scheduleDebug()) sut = conf.cronCmd(profile_id='1') self.assertNotIn('--debug', sut) diff --git a/qt/settingsdialog.py b/qt/settingsdialog.py index 902a85c51..98e596376 100644 --- a/qt/settingsdialog.py +++ b/qt/settingsdialog.py @@ -561,13 +561,13 @@ def __init__(self, parent): self.comboSchedule.currentIndexChanged.connect(self.scheduleChanged) - self.cbDebugSchedule = QCheckBox(self) - self.cbDebugSchedule.setText(_('Enable logging of debug messages')) - self.cbDebugSchedule.setToolTip(_( - 'Writes debug-level messages into the system log. Caution: Only ' - 'use this for diagnosis, as it generates a large amount of output.' + self.cbScheduleDebug = QCheckBox(self) + self.cbScheduleDebug.setText(_('Enable logging of debug messages')) + self.cbScheduleDebug.setToolTip(_( + 'Writes debug-level messages into the system log via "--debug".' + ' Caution: Only use this temporarily for diagnostics, as it generates a large amount of output!' )) - glayout.addWidget(self.cbDebugSchedule, 8, 0) + glayout.addWidget(self.cbScheduleDebug, 8, 0) # layout.addStretch() @@ -1436,7 +1436,7 @@ def updateProfile(self): self.config.scheduleRepeatedUnit()) self.updateSchedule(self.config.scheduleMode()) - self.cbDebugSchedule.setChecked(self.config.debugSchedule()) + self.cbScheduleDebug.setChecked(self.config.scheduleDebug()) # TAB: Include self.listInclude.clear() @@ -1678,7 +1678,7 @@ def saveProfile(self): self.comboScheduleRepeatedUnit.itemData( self.comboScheduleRepeatedUnit.currentIndex())) - self.config.setDebugSchedule(self.cbDebugSchedule.isChecked()) + self.config.setScheduleDebug(self.cbScheduleDebug.isChecked()) # auto-remove self.config.setRemoveOldSnapshots( From f3325e28b561c308b40067902d90f279f7ee6af6 Mon Sep 17 00:00:00 2001 From: Christian Buhtz Date: Sat, 4 May 2024 15:50:56 +0200 Subject: [PATCH 13/14] wrapped tooltip --- qt/settingsdialog.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/qt/settingsdialog.py b/qt/settingsdialog.py index 98e596376..7830e9932 100644 --- a/qt/settingsdialog.py +++ b/qt/settingsdialog.py @@ -563,10 +563,15 @@ def __init__(self, parent): self.cbScheduleDebug = QCheckBox(self) self.cbScheduleDebug.setText(_('Enable logging of debug messages')) - self.cbScheduleDebug.setToolTip(_( - 'Writes debug-level messages into the system log via "--debug".' - ' Caution: Only use this temporarily for diagnostics, as it generates a large amount of output!' - )) + qttools.set_wrapped_tooltip( + self.cbScheduleDebug, + [ + _('Writes debug-level messages into the system log via ' + '"--debug".'), + _('Caution: Only use this temporarily for diagnostics, as it ' + 'generates a large amount of output.') + ] + ) glayout.addWidget(self.cbScheduleDebug, 8, 0) # From 333f09d354e88a04ed3f269f1cef36099931340e Mon Sep 17 00:00:00 2001 From: aryoda <11374410+aryoda@users.noreply.github.com> Date: Mon, 6 May 2024 01:47:40 +0200 Subject: [PATCH 14/14] Improve understandability of CHANGES entries --- CHANGES | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bfeb4c72a..66530e91f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,9 @@ Back In Time Version 1.4.4-dev (development of upcoming release) -* Breaking Change: Argument "--debug" in crontab not used anymore when GUI runs in debug mode. -* Feature: Profile setting to activate debug output for scheduled jobs (--debug in crontab) +* Breaking Change: GUI started with --debug does no longer add --debug to the crontab for scheduled profiles. + Use the new "enable logging for debug messages" in the 'Schedule' section of the 'Manage profiles' GUI instead. +* Feature: Profile and GUI allow to activate debug output for scheduled jobs by adding '--debug' to crontab entry (#1616, contributed by @stcksmsh Kosta Vukicevic) * Feature: Support SSH proxy (jump) host (#1688) (@cgrinham, Christie Grinham) * Removed: Context menu in LogViewDialog (#1578) * Refactor: Replace Config.user() with getpass.getuser() (#1694)