From d70ee34429cc3304c5e9c00788a9f50acc45a35b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 00:29:38 +0100 Subject: [PATCH 01/10] Rename project test reference files --- ...x => coreProject_NewCustomA_nwProject.nwx} | 0 ...x => coreProject_NewCustomB_nwProject.nwx} | 0 ....nwx => coreProject_NewFile_nwProject.nwx} | 0 ...x => coreProject_NewMinimal_nwProject.nwx} | 0 ....nwx => coreProject_NewRoot_nwProject.nwx} | 0 tests/test_core_project.py | 20 +++++++++---------- 6 files changed, 10 insertions(+), 10 deletions(-) rename tests/reference/{coreProject_2_nwProject.nwx => coreProject_NewCustomA_nwProject.nwx} (100%) rename tests/reference/{coreProject_3_nwProject.nwx => coreProject_NewCustomB_nwProject.nwx} (100%) rename tests/reference/{coreProject_5_nwProject.nwx => coreProject_NewFile_nwProject.nwx} (100%) rename tests/reference/{coreProject_1_nwProject.nwx => coreProject_NewMinimal_nwProject.nwx} (100%) rename tests/reference/{coreProject_4_nwProject.nwx => coreProject_NewRoot_nwProject.nwx} (100%) diff --git a/tests/reference/coreProject_2_nwProject.nwx b/tests/reference/coreProject_NewCustomA_nwProject.nwx similarity index 100% rename from tests/reference/coreProject_2_nwProject.nwx rename to tests/reference/coreProject_NewCustomA_nwProject.nwx diff --git a/tests/reference/coreProject_3_nwProject.nwx b/tests/reference/coreProject_NewCustomB_nwProject.nwx similarity index 100% rename from tests/reference/coreProject_3_nwProject.nwx rename to tests/reference/coreProject_NewCustomB_nwProject.nwx diff --git a/tests/reference/coreProject_5_nwProject.nwx b/tests/reference/coreProject_NewFile_nwProject.nwx similarity index 100% rename from tests/reference/coreProject_5_nwProject.nwx rename to tests/reference/coreProject_NewFile_nwProject.nwx diff --git a/tests/reference/coreProject_1_nwProject.nwx b/tests/reference/coreProject_NewMinimal_nwProject.nwx similarity index 100% rename from tests/reference/coreProject_1_nwProject.nwx rename to tests/reference/coreProject_NewMinimal_nwProject.nwx diff --git a/tests/reference/coreProject_4_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx similarity index 100% rename from tests/reference/coreProject_4_nwProject.nwx rename to tests/reference/coreProject_NewRoot_nwProject.nwx diff --git a/tests/test_core_project.py b/tests/test_core_project.py index fe847983..3abefbbc 100644 --- a/tests/test_core_project.py +++ b/tests/test_core_project.py @@ -22,8 +22,8 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI): default setting, creating a Minimal project. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_1_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -70,8 +70,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): Custom type with chapters and scenes. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_NewCustomA_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_NewCustomA_nwProject.nwx") projData = { "projName": "Test Custom", @@ -111,8 +111,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI): Custom type without chapters, but with scenes. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_NewCustomB_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_NewCustomB_nwProject.nwx") projData = { "projName": "Test Custom", @@ -238,8 +238,8 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI): """Check that new root folders can be added to the project. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) @@ -274,8 +274,8 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): """Check that new files can be added to the project. """ projFile = os.path.join(fncDir, "nwProject.nwx") - testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx") - compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx") + testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx") + compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx") theProject = NWProject(dummyGUI) theProject.projTree.setSeed(42) From 7c95e6dffacc99ba52c9cbdcb5e637df4749611d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 01:06:37 +0100 Subject: [PATCH 02/10] Updated existing Error test --- pytest.ini | 2 +- tests/dummy.py | 3 ++ tests/test_base_error.py | 87 ++++++++++++++++++++++++++++++++++++++++ tests/test_error.py | 44 -------------------- 4 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 tests/test_base_error.py delete mode 100644 tests/test_error.py diff --git a/pytest.ini b/pytest.ini index b61d59ec..c2278317 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] markers = - error: Test various error handling scenarios + base: Base functionality tests core: Core functionality tests gui: Qt5 GUI tests serial diff --git a/tests/dummy.py b/tests/dummy.py index 0f26e0ff..2807578a 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -71,3 +71,6 @@ class StatusBar(): def causeOSError(*args, **kwargs): raise OSError + +def causeException(*args, **kwargs): + raise Exception diff --git a/tests/test_base_error.py b/tests/test_base_error.py new file mode 100644 index 00000000..e3bd689d --- /dev/null +++ b/tests/test_base_error.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +"""novelWriter Error Tester +""" + +import nw +import pytest + +from PyQt5.QtWidgets import qApp + +from dummy import causeException + +from nw.error import NWErrorMessage, exceptionHandler + +@pytest.mark.base +def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir): + qApp.closeAllWindows() + nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) + qtbot.addWidget(nwGUI) + nwGUI.show() + qtbot.waitForWindowShown(nwGUI) + + nwErr = NWErrorMessage(nwGUI) + qtbot.addWidget(nwErr) + nwErr.show() + + # Invalid Error Message + nwErr.setMessage(Exception, "Faulty Error", 123) + assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..." + + # Valid Error Message + nwErr.setMessage(Exception, "First Error", None) + theMessage = nwErr.msgBody.toPlainText() + assert theMessage + assert "First Error" in theMessage + assert "Exception" in theMessage + nwErr._doClose() + nwErr.close() + + # Valid Error + monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3") + theMessage = exceptionHandler(Exception, "Second Error", None, testMode=True) + assert theMessage + assert "Second Error" in theMessage + assert "Exception" in theMessage + assert "(1.2.3)" in theMessage + monkeypatch.undo() + + # No kernel version retrieved + monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException) + theMessage = exceptionHandler(Exception, "Third Error", None, testMode=True) + assert theMessage + assert "Third Error" in theMessage + assert "Exception" in theMessage + assert "(Unknown)" in theMessage + monkeypatch.undo() + + # Normal shutdown, but not testmode + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + nwGUI.mainConf.showGUI = True + exceptionHandler(Exception, "Third Error", None, testMode=False) + nwGUI.mainConf.showGUI = False + monkeypatch.undo() + + # Disable blocking of GUI + monkeypatch.setattr("PyQt5.QtWidgets.QDialog.exec_", lambda: None) + exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.undo() + + # Should handle qApp failing + monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException) + exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.undo() + + # Should handle failing to close main GUI + monkeypatch.setattr(nwGUI, "closeMain", causeException) + exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.undo() + + # Should not crash when no GUI is found + nwGUI.setObjectName("Stuff") + assert exceptionHandler(Exception, "Third Error", None, testMode=True) is None + + nwGUI.closeMain() + + # qtbot.stopForInteraction() + +# END Test testBaseError_Dialog diff --git a/tests/test_error.py b/tests/test_error.py deleted file mode 100644 index 192fb6cb..00000000 --- a/tests/test_error.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Error Tester -""" - -import nw -import pytest - -from PyQt5.QtWidgets import qApp - -from nw.error import NWErrorMessage, exceptionHandler - -@pytest.mark.error -def testErrorDialog(qtbot, fncDir, tmpDir): - qApp.closeAllWindows() - nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - - nwErr = NWErrorMessage(nwGUI) - qtbot.addWidget(nwErr) - nwErr.show() - - # Invalid Error - nwErr.setMessage(Exception, "Faulty Error", 123) - assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..." - - # Valid Error - nwErr.setMessage(Exception, "First Error", None) - theMessage = nwErr.msgBody.toPlainText() - assert theMessage - assert "First Error" in theMessage - assert "Exception" in theMessage - nwErr._doClose() - nwErr.close() - - theMessage = exceptionHandler(Exception, "Second Error", None, testMode=True) - assert theMessage - assert "Second Error" in theMessage - assert "Exception" in theMessage - - nwGUI.closeMain() - - # qtbot.stopForInteraction() From 730b572d4fe66240c70fe49e57efc60044fd9492 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 12:46:10 +0100 Subject: [PATCH 03/10] Updated the Common tests to new format --- tests/README.md | 1 + tests/{test_common.py => test_base_common.py} | 82 ++++++++++++++----- 2 files changed, 62 insertions(+), 21 deletions(-) rename tests/{test_common.py => test_base_common.py} (78%) diff --git a/tests/README.md b/tests/README.md index cd1be55b..8be3b5b5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -61,6 +61,7 @@ The commands for the respective test categories are listed below. | Type | Test Target | Source File(s) | Marker | Filter | | :--- | :----------------- | :-------------------- | :-------- | :-------------------- | +| Unit | Common functions | nw/common.py | `-m base` | `-k testBaseCommon` | | Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | diff --git a/tests/test_common.py b/tests/test_base_common.py similarity index 78% rename from tests/test_common.py rename to tests/test_base_common.py index 135627fc..8cdb8a74 100644 --- a/tests/test_common.py +++ b/tests/test_base_common.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""novelWriter Common Class Tester +"""novelWriter Common Functions Tester """ import time @@ -11,8 +11,10 @@ from nw.common import ( ) from tools import cmpList -@pytest.mark.core -def testCheckString(): +@pytest.mark.base +def testBaseCommon_CheckString(): + """Test the checkString function. + """ assert checkString(None, "NotNone", True) is None assert checkString("None", "NotNone", True) is None assert checkString("None", "NotNone", False) == "None" @@ -21,8 +23,12 @@ def testCheckString(): assert checkString(1.0, "NotNone", False) == "NotNone" assert checkString(True, "NotNone", False) == "NotNone" -@pytest.mark.core -def testCheckInt(): +# END Test testBaseCommon_CheckString + +@pytest.mark.base +def testBaseCommon_CheckInt(): + """Test the checkInt function. + """ assert checkInt(None, 3, True) is None assert checkInt("None", 3, True) is None assert checkInt(None, 3, False) == 3 @@ -30,8 +36,12 @@ def testCheckInt(): assert checkInt(1.0, 3, False) == 1 assert checkInt(True, 3, False) == 1 -@pytest.mark.core -def testCheckBool(): +# END Test testBaseCommon_CheckInt + +@pytest.mark.base +def testBaseCommon_CheckBool(): + """Test the checkBool function. + """ assert checkBool(None, 3, True) is None assert checkBool("None", 3, True) is None assert checkBool("True", False, False) @@ -44,8 +54,12 @@ def testCheckBool(): assert checkBool(1.0, None, False) is None assert checkBool(2.0, None, False) is None -@pytest.mark.core -def testCheckHandle(): +# END Test testBaseCommon_CheckBool + +@pytest.mark.base +def testBaseCommon_CheckHandle(): + """Test the checkHandle function. + """ assert checkHandle("None", 1, True) is None assert checkHandle("None", 1, False) == 1 assert checkHandle(None, 1, True) is None @@ -53,8 +67,12 @@ def testCheckHandle(): assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf" assert checkHandle("h7666c91c7ccf", None, False) is None -@pytest.mark.core -def testColRange(): +# END Test testBaseCommon_CheckHandle + +@pytest.mark.base +def testBaseCommon_ColRange(): + """Test the colRange function. + """ assert colRange([0, 0], [0, 0], 0) is None assert cmpList( colRange([200, 50, 0], [50, 200, 0], 1), @@ -77,14 +95,22 @@ def testColRange(): [[200, 50, 0], [162, 87, 0], [124, 124, 0], [86, 161, 0], [50, 200, 0]] ) -@pytest.mark.core -def testFormatTimeStamp(): +# END Test testBaseCommon_ColRange + +@pytest.mark.base +def testBaseCommon_FormatTimeStamp(): + """Test the formatTimeStamp function. + """ tTime = time.mktime(time.gmtime(0)) assert formatTimeStamp(tTime, False) == "1970-01-01 00:00:00" assert formatTimeStamp(tTime, True) == "1970-01-01 00.00.00" -@pytest.mark.core -def testFormatTime(): +# END Test testBaseCommon_FormatTimeStamp + +@pytest.mark.base +def testBaseCommon_FormatTime(): + """Test the formatTime function. + """ assert formatTime("1") == "ERROR" assert formatTime(1.0) == "ERROR" assert formatTime(1) == "00:00:01" @@ -101,8 +127,12 @@ def testFormatTime(): assert formatTime(86400) == "1-00:00:00" assert formatTime(360000) == "4-04:00:00" -@pytest.mark.core -def testFormatInt(): +# END Test testBaseCommon_FormatTime + +@pytest.mark.base +def testBaseCommon_FormatInt(): + """Test the formatInt function. + """ assert formatInt(1000) == "1000" assert formatInt(1234) == "1.23\u2009k" assert formatInt(12345) == "12.3\u2009k" @@ -112,8 +142,12 @@ def testFormatInt(): assert formatInt(123456789) == "123\u2009M" assert formatInt(1234567890) == "1.23\u2009G" -@pytest.mark.core -def testTransferCase(): +# END Test testBaseCommon_FormatInt + +@pytest.mark.base +def testBaseCommon_TransferCase(): + """Test the transferCase function. + """ assert transferCase(1, "TaRgEt") == "TaRgEt" assert transferCase("source", 1) == 1 assert transferCase("", "TaRgEt") == "TaRgEt" @@ -122,8 +156,12 @@ def testTransferCase(): assert transferCase("SOURCE", "target") == "TARGET" assert transferCase("source", "TARGET") == "target" -@pytest.mark.core -def testFuzzyTime(): +# END Test testBaseCommon_TransferCase + +@pytest.mark.base +def testBaseCommon_FuzzyTime(): + """Test the fuzzyTime function. + """ assert fuzzyTime(-1) == "in the future" assert fuzzyTime(0) == "just now" assert fuzzyTime(29) == "just now" @@ -152,3 +190,5 @@ def testFuzzyTime(): assert fuzzyTime(29808000) == "a year ago" assert fuzzyTime(47336399) == "a year ago" assert fuzzyTime(47336400) == "2 years ago" + +# END Test testBaseCommon_FuzzyTime From 33c2dda471798d0d03781da32d21816e267151b5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 13:01:49 +0100 Subject: [PATCH 04/10] Cleaned up error tests and removed testmode from error handler --- nw/error.py | 7 ++--- tests/test_base_error.py | 62 ++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/nw/error.py b/nw/error.py index 988b49e6..6db88647 100644 --- a/nw/error.py +++ b/nw/error.py @@ -135,7 +135,7 @@ class NWErrorMessage(QDialog): # END Class NWErrorMessage -def exceptionHandler(exType, exValue, exTrace, testMode=False): +def exceptionHandler(exType, exValue, exTrace): """Function to catch unhandled global exceptions. """ import nw @@ -173,10 +173,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False): logger.critical("Could not close the project before exiting") logger.critical(str(e)) - if testMode: - return errMsg.msgBody.toPlainText() - else: - qApp.exit(1) + qApp.exit(1) except Exception as e: logger.critical(str(e)) diff --git a/tests/test_base_error.py b/tests/test_base_error.py index e3bd689d..540b51f0 100644 --- a/tests/test_base_error.py +++ b/tests/test_base_error.py @@ -13,6 +13,8 @@ from nw.error import NWErrorMessage, exceptionHandler @pytest.mark.base def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir): + """Test the error dialog. + """ qApp.closeAllWindows() nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) qtbot.addWidget(nwGUI) @@ -28,60 +30,72 @@ def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir): assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..." # Valid Error Message - nwErr.setMessage(Exception, "First Error", None) + monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3") + nwErr.setMessage(Exception, "Fine Error", None) theMessage = nwErr.msgBody.toPlainText() assert theMessage - assert "First Error" in theMessage - assert "Exception" in theMessage - nwErr._doClose() - nwErr.close() - - # Valid Error - monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3") - theMessage = exceptionHandler(Exception, "Second Error", None, testMode=True) - assert theMessage - assert "Second Error" in theMessage + assert "Fine Error" in theMessage assert "Exception" in theMessage assert "(1.2.3)" in theMessage monkeypatch.undo() # No kernel version retrieved monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException) - theMessage = exceptionHandler(Exception, "Third Error", None, testMode=True) + nwErr.setMessage(Exception, "Almost Fine Error", None) + theMessage = nwErr.msgBody.toPlainText() assert theMessage - assert "Third Error" in theMessage - assert "Exception" in theMessage assert "(Unknown)" in theMessage monkeypatch.undo() - # Normal shutdown, but not testmode + nwErr._doClose() + nwErr.close() + nwGUI.closeMain() + +# END Test testBaseError_Dialog + +@pytest.mark.base +def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir): + """Test the error handler. This test doesn'thave any asserts, but it + checks that the error handler handles potential exceptions. The test + will fail if excpetions are not handled. + """ + qApp.closeAllWindows() + nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]) + qtbot.addWidget(nwGUI) + nwGUI.show() + qtbot.waitForWindowShown(nwGUI) + + # Normal shutdown monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) nwGUI.mainConf.showGUI = True - exceptionHandler(Exception, "Third Error", None, testMode=False) + exceptionHandler(Exception, "Error Message", None) nwGUI.mainConf.showGUI = False monkeypatch.undo() # Disable blocking of GUI monkeypatch.setattr("PyQt5.QtWidgets.QDialog.exec_", lambda: None) - exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + exceptionHandler(Exception, "Error Message", None) monkeypatch.undo() # Should handle qApp failing monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException) - exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + exceptionHandler(Exception, "Error Message", None) monkeypatch.undo() # Should handle failing to close main GUI monkeypatch.setattr(nwGUI, "closeMain", causeException) - exceptionHandler(Exception, "Third Error", None, testMode=True) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + exceptionHandler(Exception, "Error Message", None) monkeypatch.undo() # Should not crash when no GUI is found - nwGUI.setObjectName("Stuff") - assert exceptionHandler(Exception, "Third Error", None, testMode=True) is None + monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: []) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + exceptionHandler(Exception, "Error Message", None) + monkeypatch.undo() nwGUI.closeMain() - # qtbot.stopForInteraction() - -# END Test testBaseError_Dialog +# END Test testBaseError_Handler From cdeee65505f89b3d3768119e2153dd1b344c4f0d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 13:21:47 +0100 Subject: [PATCH 05/10] Also remove the gui blocking from the error handler --- nw/error.py | 4 +--- tests/test_base_error.py | 40 ++++++++++++++++++---------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/nw/error.py b/nw/error.py index 6db88647..24a802aa 100644 --- a/nw/error.py +++ b/nw/error.py @@ -138,7 +138,6 @@ class NWErrorMessage(QDialog): def exceptionHandler(exType, exValue, exTrace): """Function to catch unhandled global exceptions. """ - import nw import logging from traceback import print_tb from PyQt5.QtWidgets import qApp @@ -160,8 +159,7 @@ def exceptionHandler(exType, exValue, exTrace): errMsg = NWErrorMessage(nwGUI) errMsg.setMessage(exType, exValue, exTrace) - if nw.CONFIG.showGUI: - errMsg.exec_() + errMsg.exec_() try: # Try a controlled shudown diff --git a/tests/test_base_error.py b/tests/test_base_error.py index 540b51f0..472faaa2 100644 --- a/tests/test_base_error.py +++ b/tests/test_base_error.py @@ -66,33 +66,29 @@ def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir): qtbot.waitForWindowShown(nwGUI) # Normal shutdown - monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) - nwGUI.mainConf.showGUI = True - exceptionHandler(Exception, "Error Message", None) - nwGUI.mainConf.showGUI = False - monkeypatch.undo() - - # Disable blocking of GUI - monkeypatch.setattr("PyQt5.QtWidgets.QDialog.exec_", lambda: None) - monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) - exceptionHandler(Exception, "Error Message", None) - monkeypatch.undo() - - # Should handle qApp failing - monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException) - monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) - exceptionHandler(Exception, "Error Message", None) - monkeypatch.undo() - - # Should handle failing to close main GUI - monkeypatch.setattr(nwGUI, "closeMain", causeException) - monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) exceptionHandler(Exception, "Error Message", None) monkeypatch.undo() # Should not crash when no GUI is found + monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: []) - monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda x: None) + exceptionHandler(Exception, "Error Message", None) + monkeypatch.undo() + + # Should handle qApp failing + monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException) + exceptionHandler(Exception, "Error Message", None) + monkeypatch.undo() + + # Should handle failing to close main GUI + monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) + monkeypatch.setattr(nwGUI, "closeMain", causeException) exceptionHandler(Exception, "Error Message", None) monkeypatch.undo() From ee7a5e1681d3f1459911640a49a1c26345820aeb Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 6 Dec 2020 13:27:31 +0100 Subject: [PATCH 06/10] Moved the launch test from main to init --- tests/test_base_init.py | 87 +++++++++++++++++++++++++++++++++++++++++ tests/test_gui_main.py | 78 ------------------------------------ 2 files changed, 87 insertions(+), 78 deletions(-) create mode 100644 tests/test_base_init.py diff --git a/tests/test_base_init.py b/tests/test_base_init.py new file mode 100644 index 00000000..c45dc65f --- /dev/null +++ b/tests/test_base_init.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +"""novelWriter Main Init Tester +""" + +import nw +import pytest +import logging +import sys + +@pytest.mark.base +def testBaseInit_Launch(qtbot, monkeypatch, fncDir, tmpDir): + """Test the main __init__.py file. + """ + # Defaults + nwGUI = nw.main( + ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"] + ) + assert nw.logger.getEffectiveLevel() == logging.WARNING + nwGUI.closeMain() + nwGUI.close() + + # Log Levels + nwGUI = nw.main( + ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + assert nw.logger.getEffectiveLevel() == logging.INFO + nwGUI.closeMain() + nwGUI.close() + + nwGUI = nw.main( + ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + assert nw.logger.getEffectiveLevel() == logging.DEBUG + nwGUI.closeMain() + nwGUI.close() + + nwGUI = nw.main( + ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + assert nw.logger.getEffectiveLevel() == 5 + nwGUI.closeMain() + nwGUI.close() + + # Help and Version + with pytest.raises(SystemExit) as ex: + nwGUI = nw.main( + ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + nwGUI.closeMain() + nwGUI.close() + assert ex.value.code == 0 + + with pytest.raises(SystemExit) as ex: + nwGUI = nw.main( + ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + nwGUI.closeMain() + nwGUI.close() + assert ex.value.code == 0 + + # Invalid options + with pytest.raises(SystemExit) as ex: + nwGUI = nw.main( + ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + nwGUI.closeMain() + nwGUI.close() + assert ex.value.code == 2 + + # Simulate import error + monkeypatch.setitem(sys.modules, "lxml", None) + monkeypatch.setattr("sys.hexversion", 0x0) + monkeypatch.setattr("nw.CONFIG.verQtValue", 50000) + monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000) + with pytest.raises(SystemExit) as ex: + nwGUI = nw.main( + ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ) + nwGUI.closeMain() + nwGUI.close() + assert ex.value.code & 4 == 4 # Python version not satisfied + assert ex.value.code & 8 == 8 # Qt version not satisfied + assert ex.value.code & 16 == 16 # PyQt version not satisfied + assert ex.value.code & 32 == 32 # lxml package missing + monkeypatch.undo() + +# END Test testBaseInit_Launch diff --git a/tests/test_gui_main.py b/tests/test_gui_main.py index a10c0582..b56c525b 100644 --- a/tests/test_gui_main.py +++ b/tests/test_gui_main.py @@ -4,9 +4,7 @@ import nw import pytest -import logging import os -import sys from shutil import copyfile from tools import cmpFiles @@ -26,82 +24,6 @@ keyDelay = 2 typeDelay = 1 stepDelay = 20 -@pytest.mark.gui -def testLaunch(qtbot, monkeypatch, fncDir, tmpDir): - - # Defaults - nwGUI = nw.main( - ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"] - ) - assert nw.logger.getEffectiveLevel() == logging.WARNING - nwGUI.closeMain() - nwGUI.close() - - # Log Levels - nwGUI = nw.main( - ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - assert nw.logger.getEffectiveLevel() == logging.INFO - nwGUI.closeMain() - nwGUI.close() - - nwGUI = nw.main( - ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - assert nw.logger.getEffectiveLevel() == logging.DEBUG - nwGUI.closeMain() - nwGUI.close() - - nwGUI = nw.main( - ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - assert nw.logger.getEffectiveLevel() == 5 - nwGUI.closeMain() - nwGUI.close() - - # Help and Version - with pytest.raises(SystemExit) as ex: - nwGUI = nw.main( - ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - nwGUI.closeMain() - nwGUI.close() - assert ex.value.code == 0 - - with pytest.raises(SystemExit) as ex: - nwGUI = nw.main( - ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - nwGUI.closeMain() - nwGUI.close() - assert ex.value.code == 0 - - # Invalid options - with pytest.raises(SystemExit) as ex: - nwGUI = nw.main( - ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - nwGUI.closeMain() - nwGUI.close() - assert ex.value.code == 2 - - # Simulate import error - monkeypatch.setitem(sys.modules, "lxml", None) - monkeypatch.setattr("sys.hexversion", 0x0) - monkeypatch.setattr("nw.CONFIG.verQtValue", 50000) - monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000) - with pytest.raises(SystemExit) as ex: - nwGUI = nw.main( - ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir] - ) - nwGUI.closeMain() - nwGUI.close() - assert ex.value.code & 4 == 4 # Python version not satisfied - assert ex.value.code & 8 == 8 # Qt version not satisfied - assert ex.value.code & 16 == 16 # PyQt version not satisfied - assert ex.value.code & 32 == 32 # lxml package missing - monkeypatch.undo() - @pytest.mark.gui def testDocEditor(qtbot, yesToAll, fncDir, nwTempGUI, refDir, tmpDir): From a4ee1e112c2b62f3c49cb0e48098752379344a79 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 7 Dec 2020 20:00:58 +0100 Subject: [PATCH 07/10] Updated and extended the testing of the main function --- nw/__init__.py | 29 ++++----- tests/README.md | 2 + tests/dummy.py | 14 +++-- tests/test_base_init.py | 134 +++++++++++++++++++++++++++++++--------- 4 files changed, 132 insertions(+), 47 deletions(-) diff --git a/nw/__init__.py b/nw/__init__.py index a5960f30..65ab7f59 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -246,19 +246,20 @@ def main(sysArgs=None): errorCode |= 32 if errorData: - if not testMode: - errApp = QApplication([]) - errMsg = QErrorMessage() - errMsg.resize(500, 300) - errMsg.showMessage(( - "

A critical error has been encountered

" - "

novelWriter cannot start due to the following issues:

" - "

 - %s

" - "

Shutting down ...

" - ) % ( - "
 - ".join(errorData) - )) - errApp.exec_() + errApp = QApplication([]) + errMsg = QErrorMessage() + errMsg.resize(500, 300) + errMsg.showMessage(( + "

A critical error has been encountered

" + "

novelWriter cannot start due to the following issues:

" + "

 - %s

" + "

Shutting down ...

" + ) % ( + "
 - ".join(errorData) + )) + for errMsg in errorData: + logger.critical(errMsg) + errApp.exec_() sys.exit(errorCode) # Finish initialising config @@ -293,4 +294,4 @@ def main(sysArgs=None): nwGUI = GuiMain() sys.exit(nwApp.exec_()) - return +# END Function main diff --git a/tests/README.md b/tests/README.md index 8be3b5b5..09dcd6ce 100644 --- a/tests/README.md +++ b/tests/README.md @@ -61,7 +61,9 @@ The commands for the respective test categories are listed below. | Type | Test Target | Source File(s) | Marker | Filter | | :--- | :----------------- | :-------------------- | :-------- | :-------------------- | +| Unit | Main function | nw/\_\_init\_\_.py | `-m base` | `-k testBaseInit` | | Unit | Common functions | nw/common.py | `-m base` | `-k testBaseCommon` | +| Unit | Error handlers | nw/error.py | `-m base` | `-k testBaseError` | | Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` | | Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` | diff --git a/tests/dummy.py b/tests/dummy.py index 2807578a..daf0e070 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -13,7 +13,7 @@ class DummyMain(): self.hasProject = True self.theIndex = None self.theProject = None - self.statusBar = StatusBar() + self.statusBar = DummyStatusBar() # Test Variables self.askResponse = True @@ -42,6 +42,12 @@ class DummyMain(): def rebuildIndex(self): return + def closeMain(self): + return "closeMain" + + def close(self): + return "close" + # Test Functions def undo(self): @@ -52,9 +58,9 @@ class DummyMain(): self.lastAlert = "" return -# END Class GuiMain +# END Class DummyMain -class StatusBar(): +class DummyStatusBar(): def __init__(self): return @@ -62,7 +68,7 @@ class StatusBar(): def setStatus(self, theText): return -# END Class StatusBar +# END Class DummyStatusBar # =========================================================================== # # Error Functions diff --git a/tests/test_base_init.py b/tests/test_base_init.py index c45dc65f..b691bfa1 100644 --- a/tests/test_base_init.py +++ b/tests/test_base_init.py @@ -7,81 +7,157 @@ import pytest import logging import sys +from dummy import DummyMain + @pytest.mark.base -def testBaseInit_Launch(qtbot, monkeypatch, fncDir, tmpDir): - """Test the main __init__.py file. +def testBaseInit_Launch(caplog, monkeypatch, tmpDir): + """Check launching the main GUI. """ + monkeypatch.setattr("nw.guimain.GuiMain", DummyMain) + + # Testmode launch + nwGUI = nw.main( + ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir] + ) + assert isinstance(nwGUI, DummyMain) + + # Darwin launch + monkeypatch.setitem(sys.modules, "Foundation", None) + osDarwin = nw.CONFIG.osDarwin + nw.CONFIG.osDarwin = True + nwGUI = nw.main( + ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir] + ) + assert isinstance(nwGUI, DummyMain) + assert "Foundation" in caplog.messages[1] + nw.CONFIG.osDarwin = osDarwin + + # Normal launch + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *args: 0) + with pytest.raises(SystemExit) as ex: + nw.main(["--config=%s" % tmpDir, "--data=%s" % tmpDir]) + + assert ex.value.code == 0 + + monkeypatch.undo() + +# END Test testBaseInit_Launch + +@pytest.mark.base +def testBaseInit_Options(monkeypatch, tmpDir): + """Test command line options for logging level. + """ + monkeypatch.setattr("nw.guimain.GuiMain", DummyMain) + monkeypatch.setattr(sys, "argv", [ + "novelWriter.py", "--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir + ]) + + # Defaults w/None Args + nwGUI = nw.main() + assert nw.logger.getEffectiveLevel() == logging.WARNING + assert nw.CONFIG.debugInfo is False + assert nw.CONFIG.showGUI is False + assert nwGUI.closeMain() == "closeMain" + # Defaults nwGUI = nw.main( - ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"] + ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "--style=Fusion"] ) assert nw.logger.getEffectiveLevel() == logging.WARNING - nwGUI.closeMain() - nwGUI.close() + assert nw.CONFIG.debugInfo is False + assert nwGUI.closeMain() == "closeMain" # Log Levels nwGUI = nw.main( - ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--info", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.INFO - nwGUI.closeMain() - nwGUI.close() + assert nw.CONFIG.debugInfo is False + assert nwGUI.closeMain() == "closeMain" nwGUI = nw.main( - ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--debug", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.DEBUG - nwGUI.closeMain() - nwGUI.close() + assert nw.CONFIG.debugInfo is True + assert nwGUI.closeMain() == "closeMain" nwGUI = nw.main( - ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--verbose", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == 5 - nwGUI.closeMain() - nwGUI.close() + assert nw.CONFIG.debugInfo is True + assert nwGUI.closeMain() == "closeMain" # Help and Version with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--help", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) - nwGUI.closeMain() - nwGUI.close() + assert nwGUI.closeMain() == "closeMain" assert ex.value.code == 0 with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--version", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) - nwGUI.closeMain() - nwGUI.close() + assert nwGUI.closeMain() == "closeMain" assert ex.value.code == 0 # Invalid options with pytest.raises(SystemExit) as ex: nwGUI = nw.main( - ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir] + ["--testmode", "--invalid", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) - nwGUI.closeMain() - nwGUI.close() + assert nwGUI.closeMain() == "closeMain" assert ex.value.code == 2 - # Simulate import error + # Project Path + nwGUI = nw.main( + ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "sample/"] + ) + assert nw.CONFIG.cmdOpen == "sample/" + assert nwGUI.closeMain() == "closeMain" + + monkeypatch.undo() + +# END Test testBaseInit_Options + +@pytest.mark.base +def testBaseInit_Imports(caplog, monkeypatch, tmpDir): + """Check import error handling. + """ + monkeypatch.setattr("nw.guimain.GuiMain", DummyMain) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *args: 0) + monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.__init__", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.resize", lambda *args: None) + monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.showMessage", lambda *args: None) monkeypatch.setitem(sys.modules, "lxml", None) monkeypatch.setattr("sys.hexversion", 0x0) monkeypatch.setattr("nw.CONFIG.verQtValue", 50000) monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000) + with pytest.raises(SystemExit) as ex: - nwGUI = nw.main( - ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir] + _ = nw.main( + ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) - nwGUI.closeMain() - nwGUI.close() + assert ex.value.code & 4 == 4 # Python version not satisfied assert ex.value.code & 8 == 8 # Qt version not satisfied assert ex.value.code & 16 == 16 # PyQt version not satisfied assert ex.value.code & 32 == 32 # lxml package missing + + assert "At least Python" in caplog.messages[0] + assert "At least Qt5" in caplog.messages[1] + assert "At least PyQt5" in caplog.messages[2] + assert "lxml" in caplog.messages[3] + monkeypatch.undo() -# END Test testBaseInit_Launch +# END Test testBaseInit_Imports From 9772c85ece343c58579da6bf2c511e6c6aa1bea0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 7 Dec 2020 23:34:12 +0100 Subject: [PATCH 08/10] Rewritten Config class test --- nw/config.py | 91 ++-- tests/conftest.py | 3 + ...riter.conf => baseConfig_novelwriter.conf} | 0 tests/test_base_config.py | 505 ++++++++++++++++++ tests/test_config.py | 165 ------ 5 files changed, 559 insertions(+), 205 deletions(-) rename tests/reference/{novelwriter.conf => baseConfig_novelwriter.conf} (100%) create mode 100644 tests/test_base_config.py delete mode 100644 tests/test_config.py diff --git a/nw/config.py b/nw/config.py index 04c1a4db..9c232109 100644 --- a/nw/config.py +++ b/nw/config.py @@ -27,12 +27,12 @@ import logging import configparser +import shutil import json import sys import os from time import time -from shutil import which from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo @@ -44,10 +44,11 @@ logger = logging.getLogger(__name__) class Config: - CNF_STR = 0 - CNF_INT = 1 - CNF_BOOL = 2 - CNF_LIST = 3 + CNF_STR = 0 + CNF_INT = 1 + CNF_BOOL = 2 + CNF_S_LST = 3 + CNF_I_LST = 4 def __init__(self): @@ -211,12 +212,8 @@ class Config: self.osUnknown = True # Other System Info - if self.verQtValue >= 50600: - self.hostName = QSysInfo.machineHostName() - self.kernelVer = QSysInfo.kernelVersion() - else: - self.hostName = "Unknown" - self.kernelVer = "Unknown" + self.hostName = "Unknown" + self.kernelVer = "Unknown" # Packages self.hasEnchant = False # The pyenchant package @@ -321,6 +318,11 @@ class Config: self.errData.append(str(e)) self.dataPath = None + # Host and Kernel + if self.verQtValue >= 50600: + self.hostName = QSysInfo.machineHostName() + self.kernelVer = QSysInfo.kernelVersion() + # Load recent projects cache self.loadRecentCache() @@ -388,25 +390,25 @@ class Config: ## Sizes cnfSec = "Sizes" self.winGeometry = self._parseLine( - cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry + cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry ) self.treeColWidth = self._parseLine( - cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth + cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth ) self.projColWidth = self._parseLine( - cnfParse, cnfSec, "projcols", self.CNF_LIST, self.projColWidth + cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth ) self.mainPanePos = self._parseLine( - cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos + cnfParse, cnfSec, "mainpane", self.CNF_I_LST, self.mainPanePos ) self.docPanePos = self._parseLine( - cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos + cnfParse, cnfSec, "docpane", self.CNF_I_LST, self.docPanePos ) self.viewPanePos = self._parseLine( - cnfParse, cnfSec, "viewpane", self.CNF_LIST, self.viewPanePos + cnfParse, cnfSec, "viewpane", self.CNF_I_LST, self.viewPanePos ) self.outlnPanePos = self._parseLine( - cnfParse, cnfSec, "outlinepane", self.CNF_LIST, self.outlnPanePos + cnfParse, cnfSec, "outlinepane", self.CNF_I_LST, self.outlnPanePos ) self.isFullScreen = self._parseLine( cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen @@ -484,10 +486,10 @@ class Config: cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos ) self.fmtSingleQuotes = self._parseLine( - cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes + cnfParse, cnfSec, "fmtsinglequote", self.CNF_S_LST, self.fmtSingleQuotes ) self.fmtDoubleQuotes = self._parseLine( - cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes + cnfParse, cnfSec, "fmtdoublequote", self.CNF_S_LST, self.fmtDoubleQuotes ) self.spellTool = self._parseLine( cnfParse, cnfSec, "spelltool", self.CNF_STR, self.spellTool @@ -901,23 +903,28 @@ class Config: # Internal Functions ## - def _unpackList(self, inStr, listLen, listDefault, castTo=int): - """Unpack a comma separated string of items into a list. - """ - inData = inStr.split(",") - outData = [] - for i in range(listLen): - try: - outData.append(castTo(inData[i])) - except Exception: - outData.append(listDefault[i]) - return outData - def _packList(self, inData): - """Pack a list of items into a comma separated string. + """Pack a list of items into a comma-separated string. """ return ", ".join([str(inVal) for inVal in inData]) + def _unpackList(self, inStr, listDefault, cnfType): + """Unpack a comma-separated string of items into a list. + """ + inData = inStr.split(",") + outData = listDefault.copy() + for i in range(min(len(inData), len(listDefault))): + try: + if cnfType == self.CNF_S_LST: + outData[i] = inData[i].strip() + elif cnfType == self.CNF_I_LST: + outData[i] = int(inData[i].strip()) + else: + continue + except Exception: + continue + return outData + def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault): """Parse a line and return the correct datatype. """ @@ -930,18 +937,24 @@ class Config: return cnfParse.getint(cnfSec, cnfName) elif cnfType == self.CNF_BOOL: return cnfParse.getboolean(cnfSec, cnfName) - elif cnfType == self.CNF_LIST: + elif cnfType == self.CNF_I_LST: return self._unpackList( - cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault + cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_I_LST + ) + elif cnfType == self.CNF_S_LST: + return self._unpackList( + cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST ) except ValueError as e: logger.error("Failed to load value from config file.") logger.error(str(e)) + return cnfDefault return cnfDefault def _checkNone(self, checkVal): - """Convert a string to a none type. + """Return a NoneType if the value correspomds to None, otherwise + return the value unchanged. """ if checkVal is None: return None @@ -961,10 +974,8 @@ class Config: self.hasEnchant = False logger.debug("Checking package 'pyenchant': Missing") - try: - self.hasAssistant = which("assistant") - except Exception: - self.hasAssistant = False + assistPath = shutil.which("assistant") + self.hasAssistant = assistPath is not None if self.hasAssistant: logger.debug("Checking executable 'assistant': Ok") else: diff --git a/tests/conftest.py b/tests/conftest.py index 0d66d0ea..ea9a92b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,6 +73,9 @@ def fncDir(tmpDir): def tmpConf(tmpDir): """Create a temporary novelWriter configuration object. """ + confFile = os.path.join(tmpDir, "novelwriter.conf") + if os.path.isfile(confFile): + os.unlink(confFile) theConf = Config() theConf.initConfig(tmpDir, tmpDir) theConf.setLastPath("") diff --git a/tests/reference/novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf similarity index 100% rename from tests/reference/novelwriter.conf rename to tests/reference/baseConfig_novelwriter.conf diff --git a/tests/test_base_config.py b/tests/test_base_config.py new file mode 100644 index 00000000..f7aacc50 --- /dev/null +++ b/tests/test_base_config.py @@ -0,0 +1,505 @@ +# -*- coding: utf-8 -*- +"""novelWriter Config Class Tester +""" + +import pytest +import sys +import os +import configparser + +from shutil import copyfile + +from dummy import causeOSError +from tools import cmpFiles, readFile + +from nw.config import Config +from nw.constants import nwConst, nwFiles + +@pytest.mark.base +def testBaseConfig_Constructor(monkeypatch): + """Test config contructor. + """ + # Linux + monkeypatch.setattr("sys.platform", "linux") + tstConf = Config() + assert tstConf.osLinux is True + assert tstConf.osDarwin is False + assert tstConf.osWindows is False + assert tstConf.osUnknown is False + monkeypatch.undo() + + # macOS + monkeypatch.setattr("sys.platform", "darwin") + tstConf = Config() + assert tstConf.osLinux is False + assert tstConf.osDarwin is True + assert tstConf.osWindows is False + assert tstConf.osUnknown is False + monkeypatch.undo() + + # Windows + monkeypatch.setattr("sys.platform", "win32") + tstConf = Config() + assert tstConf.osLinux is False + assert tstConf.osDarwin is False + assert tstConf.osWindows is True + assert tstConf.osUnknown is False + monkeypatch.undo() + + # Cygwin + monkeypatch.setattr("sys.platform", "cygwin") + tstConf = Config() + assert tstConf.osLinux is False + assert tstConf.osDarwin is False + assert tstConf.osWindows is True + assert tstConf.osUnknown is False + monkeypatch.undo() + + # Other + monkeypatch.setattr("sys.platform", "some_ther_os") + tstConf = Config() + assert tstConf.osLinux is False + assert tstConf.osDarwin is False + assert tstConf.osWindows is False + assert tstConf.osUnknown is True + monkeypatch.undo() + +# END Test testBaseConfig_Constructor + +@pytest.mark.base +def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir): + """Test config intialisation. + """ + tstConf = Config() + + confFile = os.path.join(tmpDir, "novelwriter.conf") + testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") + compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") + + # Make sure we don't have any old conf file + if os.path.isfile(confFile): + os.unlink(confFile) + + # Let the config class figure out the path + monkeypatch.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir) + tstConf.verQtValue = 50600 + tstConf.initConfig() + assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) + assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) + assert not os.path.isfile(confFile) + tstConf.verQtValue = 50000 + tstConf.initConfig() + assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) + assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) + assert not os.path.isfile(confFile) + monkeypatch.undo() + + # Fail to make folders + monkeypatch.setattr("os.mkdir", causeOSError) + + tstConfDir = os.path.join(fncDir, "test_conf") + tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir) + assert tstConf.confPath is None + assert tstConf.dataPath == tmpDir + assert not os.path.isfile(confFile) + + tstDataDir = os.path.join(fncDir, "test_data") + tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir) + assert tstConf.confPath == tmpDir + assert tstConf.dataPath is None + assert os.path.isfile(confFile) + os.unlink(confFile) + + monkeypatch.undo() + + # Test load/save with no path + tstConf.confPath = None + assert not tstConf.loadConfig() + assert not tstConf.saveConfig() + + # Run again and set the paths directly and correctly + # This should create a config file as well + monkeypatch.setattr("os.path.expanduser", lambda *args: "") + tstConf.spellTool = nwConst.SP_INTERNAL + tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) + assert tstConf.confPath == tmpDir + assert tstConf.dataPath == tmpDir + assert os.path.isfile(confFile) + + copyfile(confFile, testFile) + assert cmpFiles(testFile, compFile, [2, 9]) + monkeypatch.undo() + + # Load and save with OSError + monkeypatch.setattr("builtins.open", causeOSError) + + assert not tstConf.loadConfig() + assert tstConf.hasError is True + assert tstConf.errData != [] + assert tstConf.getErrData().startswith("Could not") + assert tstConf.hasError is False + assert tstConf.errData == [] + + assert not tstConf.saveConfig() + assert tstConf.hasError is True + assert tstConf.errData != [] + assert tstConf.getErrData().startswith("Could not") + assert tstConf.hasError is False + assert tstConf.errData == [] + + monkeypatch.undo() + + assert tstConf.loadConfig() + assert tstConf.saveConfig() + + copyfile(confFile, testFile) + assert cmpFiles(testFile, compFile, [2, 9]) + +# END Test testBaseConfig_Init + +@pytest.mark.base +def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir): + """Test recent cache file. + """ + # Check failing + tmpConf.dataPath = None + assert not tmpConf.loadRecentCache() + assert not tmpConf.saveRecentCache() + tmpConf.dataPath = tmpDir + + # Add a couple of values + pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE) + pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE) + assert tmpConf.updateRecentCache(pathOne, "Proj One", 100, 1600002000) + assert tmpConf.updateRecentCache(pathTwo, "Proj Two", 200, 1600005600) + assert tmpConf.recentProj == { + pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, + pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, + } + + # Fail to Save + monkeypatch.setattr("builtins.open", causeOSError) + assert not tmpConf.saveRecentCache() + monkeypatch.undo() + + # Save Proper + cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE) + assert tmpConf.saveRecentCache() + assert tmpConf.saveRecentCache() + assert os.path.isfile(cacheFile) + + # Fail to Load + monkeypatch.setattr("builtins.open", causeOSError) + tmpConf.recentProj = {} + assert not tmpConf.loadRecentCache() + assert tmpConf.recentProj == {} + monkeypatch.undo() + + # Load Proper + tmpConf.recentProj = {} + assert tmpConf.loadRecentCache() + assert tmpConf.recentProj == { + pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, + pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, + } + + # Remove Non-Existent Entry + assert not tmpConf.removeFromRecentCache("dummy") + assert tmpConf.recentProj == { + pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, + pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200}, + } + + # Remove Second Entry + assert tmpConf.removeFromRecentCache(pathTwo) + assert tmpConf.recentProj == { + pathOne: {"time": 1600002000, "title": "Proj One", "words": 100}, + } + +# END Test testBaseConfig_RecentCache + +@pytest.mark.base +def testBaseConfig_SetPath(tmpConf, tmpDir): + """Test path setters. + """ + # Conf Path + assert tmpConf.setConfPath(None) + assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow")) + assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf")) + assert tmpConf.confPath == tmpDir + assert tmpConf.confFile == "novelwriter.conf" + assert not tmpConf.confChanged + + # Data Path + assert tmpConf.setDataPath(None) + assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow")) + assert tmpConf.setDataPath(tmpDir) + assert tmpConf.dataPath == tmpDir + assert not tmpConf.confChanged + + # Last Path + assert tmpConf.setLastPath(None) + assert tmpConf.lastPath == "" + + assert tmpConf.setLastPath(os.path.join(tmpDir, "file.tmp")) + assert tmpConf.lastPath == tmpDir + + assert tmpConf.setLastPath("") + assert tmpConf.lastPath == "" + +# END Test testBaseConfig_SetPath + +@pytest.mark.base +def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir): + """Set various sizes and positions + """ + confFile = os.path.join(tmpDir, "novelwriter.conf") + testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") + compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") + + # GUI Scaling + # =========== + tmpConf.guiScale = 1.0 + assert tmpConf.pxInt(10) == 10 + assert tmpConf.pxInt(13) == 13 + assert tmpConf.rpxInt(10) == 10 + assert tmpConf.rpxInt(13) == 13 + + tmpConf.guiScale = 2.0 + assert tmpConf.pxInt(10) == 20 + assert tmpConf.pxInt(13) == 26 + assert tmpConf.rpxInt(10) == 5 + assert tmpConf.rpxInt(13) == 6 + + # Setter + Getter Combos + # ====================== + + # Window Size + tmpConf.guiScale = 1.0 + assert tmpConf.setWinSize(1205, 655) + assert not tmpConf.confChanged + + tmpConf.guiScale = 2.0 + assert tmpConf.setWinSize(70, 70) + assert tmpConf.getWinSize() == [70, 70] + assert tmpConf.winGeometry == [35, 35] + + tmpConf.guiScale = 1.0 + assert tmpConf.setWinSize(70, 70) + assert tmpConf.getWinSize() == [70, 70] + assert tmpConf.winGeometry == [70, 70] + + assert tmpConf.setWinSize(1200, 650) + + # Project Tree Columns + tmpConf.guiScale = 2.0 + assert tmpConf.setTreeColWidths([10, 20, 25]) + assert tmpConf.getTreeColWidths() == [10, 20, 24] + assert tmpConf.treeColWidth == [5, 10, 12] + + tmpConf.guiScale = 1.0 + assert tmpConf.setTreeColWidths([10, 20, 25]) + assert tmpConf.getTreeColWidths() == [10, 20, 25] + assert tmpConf.treeColWidth == [10, 20, 25] + + assert tmpConf.setTreeColWidths([200, 50, 30]) + + # Project Settings Tree Columns + tmpConf.guiScale = 2.0 + assert tmpConf.setProjColWidths([10, 20, 30]) + assert tmpConf.getProjColWidths() == [10, 20, 30] + assert tmpConf.projColWidth == [5, 10, 15] + + tmpConf.guiScale = 1.0 + assert tmpConf.setProjColWidths([10, 20, 30]) + assert tmpConf.getProjColWidths() == [10, 20, 30] + assert tmpConf.projColWidth == [10, 20, 30] + + assert tmpConf.setProjColWidths([200, 60, 140]) + + # Main Pane Splitter + tmpConf.guiScale = 2.0 + assert tmpConf.setMainPanePos([200, 700]) + assert tmpConf.getMainPanePos() == [200, 700] + assert tmpConf.mainPanePos == [100, 350] + + tmpConf.guiScale = 1.0 + assert tmpConf.setMainPanePos([200, 700]) + assert tmpConf.getMainPanePos() == [200, 700] + assert tmpConf.mainPanePos == [200, 700] + + assert tmpConf.setMainPanePos([300, 800]) + + # Doc Pane Splitter + tmpConf.guiScale = 2.0 + assert tmpConf.setDocPanePos([300, 300]) + assert tmpConf.getDocPanePos() == [300, 300] + assert tmpConf.docPanePos == [150, 150] + + tmpConf.guiScale = 1.0 + assert tmpConf.setDocPanePos([300, 300]) + assert tmpConf.getDocPanePos() == [300, 300] + assert tmpConf.docPanePos == [300, 300] + + assert tmpConf.setDocPanePos([400, 400]) + + # View Pane Splitter + tmpConf.guiScale = 2.0 + assert tmpConf.setViewPanePos([400, 250]) + assert tmpConf.getViewPanePos() == [400, 250] + assert tmpConf.viewPanePos == [200, 125] + + tmpConf.guiScale = 1.0 + assert tmpConf.setViewPanePos([400, 250]) + assert tmpConf.getViewPanePos() == [400, 250] + assert tmpConf.viewPanePos == [400, 250] + + assert tmpConf.setViewPanePos([500, 150]) + + # Outline Pane Splitter + tmpConf.guiScale = 2.0 + assert tmpConf.setOutlinePanePos([400, 250]) + assert tmpConf.getOutlinePanePos() == [400, 250] + assert tmpConf.outlnPanePos == [200, 125] + + tmpConf.guiScale = 1.0 + assert tmpConf.setOutlinePanePos([400, 250]) + assert tmpConf.getOutlinePanePos() == [400, 250] + assert tmpConf.outlnPanePos == [400, 250] + + assert tmpConf.setOutlinePanePos([500, 150]) + + # Getters Only + # ============ + tmpConf.guiScale = 1.0 + assert tmpConf.getTextWidth() == 600 + assert tmpConf.getTextMargin() == 40 + assert tmpConf.getTabWidth() == 40 + assert tmpConf.getFocusWidth() == 800 + + tmpConf.guiScale = 2.0 + assert tmpConf.getTextWidth() == 1200 + assert tmpConf.getTextMargin() == 80 + assert tmpConf.getTabWidth() == 80 + assert tmpConf.getFocusWidth() == 1600 + + # Flag Setters + # ============ + assert not tmpConf.setShowRefPanel(False) + assert not tmpConf.showRefPanel + assert tmpConf.setShowRefPanel(True) + + assert not tmpConf.setViewComments(False) + assert not tmpConf.viewComments + assert tmpConf.setViewComments(True) + + assert not tmpConf.setViewSynopsis(False) + assert not tmpConf.viewSynopsis + assert tmpConf.setViewSynopsis(True) + + # Check Final File + # ================ + + assert tmpConf.confChanged + assert tmpConf.saveConfig() + assert not tmpConf.confChanged + + copyfile(confFile, testFile) + assert cmpFiles(testFile, compFile, [2, 9]) + +# END Test testBaseConfig_SettersGetters + +@pytest.mark.base +def testBaseConfig_Internal(monkeypatch, tmpConf): + """Check internal functions. + """ + # Function _packList + assert tmpConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False" + + # Function _unpackList + assert tmpConf._unpackList("1, 2, 3", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 3] + assert tmpConf._unpackList("1, 2 ", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 0] + assert tmpConf._unpackList("A, B, C", [0, 0, 0], tmpConf.CNF_I_LST) == [0, 0, 0] + assert tmpConf._unpackList("1, 2, 3", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["1", "2", "3"] + assert tmpConf._unpackList("A, B ", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "Z"] + assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "C"] + assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_STR) == ["X", "Y", "Z"] + + # Function _parseLine + cnfParse = configparser.ConfigParser() + cnfParse.read_string( + "[Main]\n" + "val_string = dummy\n" + "val_int = 123\n" + "val_bool = True\n" + "val_list_string = A, B, C\n" + "val_list_int = 1, 2, 3\n" + ) + + assert tmpConf._parseLine( + cnfParse, "Main", "val_string", tmpConf.CNF_STR, "default" + ) == "dummy" + assert tmpConf._parseLine( + cnfParse, "Main", "nope", tmpConf.CNF_STR, "default" + ) == "default" + + assert tmpConf._parseLine( + cnfParse, "Main", "val_int", tmpConf.CNF_INT, "0" + ) == 123 + assert tmpConf._parseLine( + cnfParse, "Main", "nope", tmpConf.CNF_INT, 0 + ) == 0 + assert tmpConf._parseLine( + cnfParse, "Main", "val_string", tmpConf.CNF_INT, 0 + ) == 0 + + assert tmpConf._parseLine( + cnfParse, "Main", "val_bool", tmpConf.CNF_BOOL, False + ) is True + assert tmpConf._parseLine( + cnfParse, "Main", "nope", tmpConf.CNF_BOOL, False + ) is False + assert tmpConf._parseLine( + cnfParse, "Main", "val_string", tmpConf.CNF_BOOL, False + ) is False + + assert tmpConf._parseLine( + cnfParse, "Main", "val_list_string", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"] + ) == ["A", "B", "C", "Z"] + assert tmpConf._parseLine( + cnfParse, "Main", "nope", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"] + ) == ["W", "X", "Y", "Z"] + + assert tmpConf._parseLine( + cnfParse, "Main", "val_list_int", tmpConf.CNF_I_LST, [6, 7, 8, 9] + ) == [1, 2, 3, 9] + assert tmpConf._parseLine( + cnfParse, "Main", "nope", tmpConf.CNF_S_LST, [6, 7, 8, 9] + ) == [6, 7, 8, 9] + + # Function _checkNone + assert tmpConf._checkNone(None) is None + assert tmpConf._checkNone("None") is None + assert tmpConf._checkNone("stuff") == "stuff" + + # Function _checkOptionalPackages + # (Assumes enchant package exists ans is importable) + tmpConf._checkOptionalPackages() + assert tmpConf.hasEnchant is True + + monkeypatch.setitem(sys.modules, "enchant", None) + tmpConf._checkOptionalPackages() + assert tmpConf.hasEnchant is False + monkeypatch.undo() + + monkeypatch.setattr("shutil.which", lambda *args: "dummy") + tmpConf._checkOptionalPackages() + assert tmpConf.hasAssistant is True + monkeypatch.undo() + + monkeypatch.setattr("shutil.which", lambda *args: None) + tmpConf._checkOptionalPackages() + assert tmpConf.hasAssistant is False + monkeypatch.undo() + +# END Test testBaseConfig_Internal diff --git a/tests/test_config.py b/tests/test_config.py deleted file mode 100644 index 94ff5d1c..00000000 --- a/tests/test_config.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Config Class Tester -""" - -import pytest -import os - -from tools import cmpFiles - -@pytest.mark.core -def testConfigCore(tmpConf, tmpDir, refDir): - refConf = os.path.join(refDir, "novelwriter.conf") - testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - - assert tmpConf.confPath == tmpDir - assert tmpConf.saveConfig() - assert cmpFiles(testConf, refConf, [2, 9]) - assert not tmpConf.confChanged - - assert tmpConf.loadConfig() - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigSetConfPath(tmpConf, tmpDir): - assert tmpConf.setConfPath(None) - assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow")) - assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf")) - assert tmpConf.confPath == tmpDir - assert tmpConf.confFile == "novelwriter.conf" - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigSetDataPath(tmpConf, tmpDir): - assert tmpConf.setDataPath(None) - assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow")) - assert tmpConf.setDataPath(tmpDir) - assert tmpConf.dataPath == tmpDir - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigSetWinSize(tmpConf, tmpDir, refDir): - refConf = os.path.join(refDir, "novelwriter.conf") - testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - tmpConf.guiScale = 1.0 - - assert tmpConf.confPath == tmpDir - assert tmpConf.setWinSize(1205, 655) - assert not tmpConf.confChanged - assert tmpConf.setWinSize(70, 70) - assert tmpConf.confChanged - assert tmpConf.setWinSize(1200, 650) - assert tmpConf.saveConfig() - - assert cmpFiles(testConf, refConf, [2, 9]) - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigSetTreeColWidths(tmpConf, tmpDir, refDir): - refConf = os.path.join(refDir, "novelwriter.conf") - testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - - assert tmpConf.confPath == tmpDir - tmpConf.guiScale = 1.0 - - assert tmpConf.setTreeColWidths([10, 20, 25]) - assert tmpConf.treeColWidth == [10, 20, 25] - assert tmpConf.setTreeColWidths([200, 50, 30]) - - assert tmpConf.setProjColWidths([10, 20, 30]) - assert tmpConf.projColWidth == [10, 20, 30] - assert tmpConf.setProjColWidths([200, 60, 140]) - - assert tmpConf.confChanged - assert tmpConf.saveConfig() - - assert cmpFiles(testConf, refConf, [2, 9]) - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigSetPanePos(tmpConf, tmpDir, refDir): - refConf = os.path.join(refDir, "novelwriter.conf") - testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - - assert tmpConf.confPath == tmpDir - - tmpConf.guiScale = 2.0 - assert tmpConf.setMainPanePos([200, 700]) - assert tmpConf.mainPanePos == [100, 350] - assert tmpConf.getMainPanePos() == [200, 700] - - assert tmpConf.setDocPanePos([300, 300]) - assert tmpConf.docPanePos == [150, 150] - assert tmpConf.getDocPanePos() == [300, 300] - - assert tmpConf.setViewPanePos([400, 250]) - assert tmpConf.viewPanePos == [200, 125] - assert tmpConf.getViewPanePos() == [400, 250] - - assert tmpConf.setOutlinePanePos([400, 250]) - assert tmpConf.outlnPanePos == [200, 125] - assert tmpConf.getOutlinePanePos() == [400, 250] - - tmpConf.guiScale = 1.0 - assert tmpConf.setMainPanePos([300, 800]) - assert tmpConf.setDocPanePos([400, 400]) - assert tmpConf.setViewPanePos([500, 150]) - assert tmpConf.setOutlinePanePos([500, 150]) - - assert tmpConf.confChanged - assert tmpConf.saveConfig() - - assert cmpFiles(testConf, refConf, [2, 9]) - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigFlags(tmpConf, tmpDir, refDir): - refConf = os.path.join(refDir, "novelwriter.conf") - testConf = os.path.join(tmpConf.confPath, "novelwriter.conf") - - assert tmpConf.confPath == tmpDir - - assert not tmpConf.setShowRefPanel(False) - assert tmpConf.setShowRefPanel(True) - - assert not tmpConf.setViewComments(False) - assert not tmpConf.viewComments - assert tmpConf.setViewComments(True) - - assert not tmpConf.setViewSynopsis(False) - assert not tmpConf.viewSynopsis - assert tmpConf.setViewSynopsis(True) - - assert tmpConf.confChanged - assert tmpConf.saveConfig() - - assert cmpFiles(testConf, refConf, [2, 9]) - assert not tmpConf.confChanged - -@pytest.mark.core -def testTextSizes(tmpConf, tmpDir, refDir): - assert tmpConf.confPath == tmpDir - - tmpConf.guiScale = 2.0 - assert tmpConf.getTextWidth() == 1200 - assert tmpConf.getTextMargin() == 80 - assert tmpConf.getTabWidth() == 80 - assert tmpConf.getFocusWidth() == 1600 - tmpConf.guiScale = 1.0 - - assert not tmpConf.confChanged - -@pytest.mark.core -def testConfigErrors(tmpConf): - nonPath = os.path.join("somewhere", "over", "the", "rainbow") - assert tmpConf.initConfig(nonPath, nonPath) - assert tmpConf.hasError - assert not tmpConf.loadConfig() - assert not tmpConf.saveConfig() - assert not tmpConf.loadRecentCache() - assert len(tmpConf.getErrData()) > 0 - -@pytest.mark.core -def testConfigInternals(tmpConf): - assert tmpConf._checkNone(None) is None - assert tmpConf._checkNone("None") is None From 330ee77e58fc7fa51b527258edd91a3e2a2ad608 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 7 Dec 2020 23:39:54 +0100 Subject: [PATCH 09/10] Fix flake8 fail --- tests/test_base_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_base_config.py b/tests/test_base_config.py index f7aacc50..fe755646 100644 --- a/tests/test_base_config.py +++ b/tests/test_base_config.py @@ -10,7 +10,7 @@ import configparser from shutil import copyfile from dummy import causeOSError -from tools import cmpFiles, readFile +from tools import cmpFiles from nw.config import Config from nw.constants import nwConst, nwFiles From 1469953b5e452effc75dd0726ca733383c6a6f3d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 7 Dec 2020 23:45:14 +0100 Subject: [PATCH 10/10] Updated test README --- tests/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/README.md b/tests/README.md index 09dcd6ce..7d8e378d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -63,6 +63,7 @@ The commands for the respective test categories are listed below. | :--- | :----------------- | :-------------------- | :-------- | :-------------------- | | Unit | Main function | nw/\_\_init\_\_.py | `-m base` | `-k testBaseInit` | | Unit | Common functions | nw/common.py | `-m base` | `-k testBaseCommon` | +| Unit | Config class | nw/config.py | `-m base` | `-k testBaseConfig` | | Unit | Error handlers | nw/error.py | `-m base` | `-k testBaseError` | | Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` | | Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |