Add test coverage of storage class

This commit is contained in:
Veronica Berglyd Olsen
2022-11-08 22:19:22 +01:00
parent ee79d3d6d6
commit f5a3280565
4 changed files with 349 additions and 16 deletions
+2
View File
@@ -422,6 +422,7 @@ class NWProject(QObject):
# Save other project data
self._options.saveSettings()
self._index.saveIndex()
self._storage.runPostSaveTasks(autoSave=autoSave)
# Update recent projects
self.mainConf.updateRecentCache(
@@ -443,6 +444,7 @@ class NWProject(QObject):
self._tree.writeToCFile()
self._appendSessionStats(idleTime)
self._storage.clearLockFile()
self._storage.closeSession()
self.clearProject()
self.lockedBy = None
return True
+5 -5
View File
@@ -113,10 +113,10 @@ class NWStorage:
return True
def openProjectArchive(self, path):
def openProjectArchive(self, path): # pragma: no cover
pass
def runPostSaveTasks(self, autoSave=False):
def runPostSaveTasks(self, autoSave=False): # pragma: no cover
"""Run tasks after the project has been saved.
"""
if self._openMode == self.MODE_INPLACE:
@@ -295,9 +295,8 @@ class NWStorage:
if newProject:
# If it's a new project, we check that there is no existing
# project in the selected path.
projFile = path / nwFiles.PROJ_FILE
if projFile.exists():
logger.error("A project already exists in this path")
if path.exists() and len(list(path.iterdir())) > 0:
logger.error("The new project folder is not empty")
self.clear()
return False
@@ -310,6 +309,7 @@ class NWStorage:
(path / "meta").mkdir(exist_ok=True)
except Exception as exc:
logger.error("Failed to create required project folders", exc_info=exc)
self.clear()
return False
if not checkLegacy:
+10 -11
View File
@@ -29,7 +29,7 @@ from pathlib import Path
from mock import MockGuiMain
from tools import cleanProject
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
import novelwriter # noqa: E402
@@ -64,15 +64,15 @@ def tmpDir():
return theDir
@pytest.fixture(scope="function")
@pytest.fixture(scope="session")
def tmpPath(tmpDir):
"""A temporary folder for a single test function.
"""A temporary folder for the test session. Path version.
"""
return Path(tmpDir)
@pytest.fixture(scope="session")
def tstPaths(tmpDir):
def tstPaths(tmpPath):
"""Returns an object that can provide the various paths needed for
running tests.
"""
@@ -80,7 +80,7 @@ def tstPaths(tmpDir):
testDir = Path(__file__).parent
filesDir = testDir / "files"
refDir = testDir / "reference"
outDir = testDir / tmpDir / "results"
outDir = tmpPath / "results"
store = _Store()
store.outDir.mkdir(exist_ok=True)
@@ -89,14 +89,13 @@ def tstPaths(tmpDir):
@pytest.fixture(scope="function")
def fncPath(tmpDir):
"""A temporary folder for a single test function.
def fncPath(tmpPath):
"""A temporary folder for a single test function. Path version.
"""
fncPath = Path(tmpDir) / "f_temp"
fncPath = tmpPath / "function"
if fncPath.is_dir():
shutil.rmtree(fncPath)
if not fncPath.is_dir():
fncPath.mkdir()
fncPath.mkdir(exist_ok=True)
return fncPath
@@ -133,7 +132,7 @@ def outDir(tmpDir):
def fncDir(tmpDir):
"""A temporary folder for a single test function.
"""
fncDir = os.path.join(tmpDir, "f_temp")
fncDir = os.path.join(tmpDir, "function")
if os.path.isdir(fncDir):
shutil.rmtree(fncDir)
if not os.path.isdir(fncDir):
+332
View File
@@ -0,0 +1,332 @@
"""
novelWriter NWStorage Class Tester
====================================
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
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 3 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, see <https://www.gnu.org/licenses/>.
"""
from zipfile import ZipFile
import pytest
from mock import causeOSError
from tools import C, buildTestProject, writeFile
from novelwriter.constants import nwFiles
from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorage
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
class MockProject:
pass
@pytest.mark.core
def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
"""Test opening a project in a folder.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncPath)
theProject.closeProject()
# Create instance
storage = NWStorage(theProject)
# Check defaults
assert storage.storagePath is None
assert storage.runtimePath is None
assert storage.contentPath is None
assert storage._openMode == NWStorage.MODE_INACTIVE
# Check closed project return values
assert storage.isOpen() is False
assert storage.getXmlReader() is None
assert storage.getXmlWriter() is None
assert bool(storage.getDocument(C.hSceneDoc)) is False
assert storage.getMetaFile("file") is None
assert storage.getCacheFile("file") is None
# Open project as a new project should fail
assert storage.openProjectInPlace(fncPath, newProject=True) is False
# Opening as a no-new project is fine
assert storage.openProjectInPlace(fncPath, newProject=False) is True
# Opening the project file is also fine
assert storage.openProjectInPlace(fncPath / nwFiles.PROJ_FILE, newProject=False) is True
# Check settings
assert storage.storagePath == fncPath
assert storage.runtimePath == fncPath
assert storage.contentPath == fncPath / "content"
assert storage._openMode == NWStorage.MODE_INPLACE
# Open the project itself
theProject.openProject(fncPath)
storage = theProject.storage
# Get XML components
assert isinstance(storage.getXmlReader(), ProjectXMLReader)
assert isinstance(storage.getXmlWriter(), ProjectXMLWriter)
# Get document
assert storage.getDocument(C.hSceneDoc).readDocument() == "### New Scene\n\n"
# Get paths
assert storage.getMetaFile("stuff") == fncPath / "meta" / "stuff"
assert storage.getCacheFile("stuff") == fncPath / "cache" / "stuff"
# Clean up
assert theProject.closeProject() is True
# Check closed project return values (again)
assert storage.isOpen() is False
assert storage.getXmlReader() is None
assert storage.getXmlWriter() is None
assert bool(storage.getDocument(C.hSceneDoc)) is False
assert storage.getMetaFile("file") is None
assert storage.getCacheFile("file") is None
# END Test testCoreStorage_ProjectInPlace
@pytest.mark.core
def testCoreStorage_LockFile(monkeypatch, fncPath):
"""Test the project lock file.
"""
monkeypatch.setattr("novelwriter.core.storage.time", lambda: 1000.0)
storage = NWStorage(MockProject())
assert storage.isOpen() is False
# Project not open, so cannot read/write lock file
assert storage.readLockFile() == ["ERROR"]
assert storage.writeLockFile() is False
assert storage.clearLockFile() is False
# Set a path to work with
lockFilePath = fncPath / nwFiles.PROJ_LOCK
storage._lockFilePath = lockFilePath
# Path is set, but there is no lockfile
assert storage.readLockFile() == []
# Write lockfile fails
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.write_text", causeOSError)
assert storage.writeLockFile() is False
assert not lockFilePath.exists()
# Successful write
assert storage.writeLockFile() is True
assert lockFilePath.exists()
assert lockFilePath.read_text().split(";")[3] == "1000"
# Read lockfile fails
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.read_text", causeOSError)
assert storage.readLockFile() == ["ERROR"]
assert lockFilePath.exists()
# Successful read
assert storage.readLockFile() == [
storage.mainConf.hostName,
storage.mainConf.osType,
storage.mainConf.kernelVer,
"1000",
]
# Write an invalid lockfile
writeFile(lockFilePath, "a;b;c")
assert storage.readLockFile() == ["ERROR"]
# Fail to remove lockfile
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.unlink", causeOSError)
assert storage.clearLockFile() is False
assert lockFilePath.exists()
# Successful remove
assert storage.clearLockFile() is True
assert not lockFilePath.exists()
# END Test testCoreStorage_LockFile
@pytest.mark.core
def testCoreStorage_PrepareStorage(monkeypatch, fncPath):
"""Test the project path preparation functions.
"""
storage = NWStorage(MockProject())
assert storage.isOpen() is False
# No path set
assert storage._prepareStorage() is False
# Set path to home
storage._runtimePath = fncPath
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.home", lambda: fncPath)
assert storage._prepareStorage() is False
# Fail on mkdir
storage._runtimePath = fncPath
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.mkdir", causeOSError)
assert storage._prepareStorage() is False
# Set up the folder
storage._runtimePath = fncPath
assert storage._prepareStorage(checkLegacy=False) is True
assert (fncPath / "content").exists()
assert (fncPath / "cache").exists()
assert (fncPath / "meta").exists()
# Add a legacy folder
storage._runtimePath = fncPath
dataDir = fncPath / "data_0"
dataDir.mkdir()
assert storage._prepareStorage(checkLegacy=True) is True
assert not dataDir.exists()
# We cannot add a new project here
storage._runtimePath = fncPath
assert storage._prepareStorage(checkLegacy=False, newProject=True) is False
# Legacy Data Folder
# ==================
storage._runtimePath = fncPath
data = []
files = []
for c in "0123456789abcdefX":
dataDir = fncPath / f"data_{c}"
dataDir.mkdir()
data.append(dataDir)
nwdFile = dataDir / f"00000000000{c}_main.nwd"
bakFile = dataDir / f"00000000000{c}_main.bak"
nwdFile.write_text("#")
bakFile.write_text("#")
files.append(nwdFile)
files.append(bakFile)
for item in files:
assert item.exists()
# Pollute folder 7 and 8
(data[7] / "stuff.txt").write_text("foo")
(data[8] / "bar").mkdir()
# Process folders
for i in range(9):
storage._legacyDataFolder(fncPath, data[i])
# Files form 0 to 8 should now be in content
for c in "012345678":
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
# Folders 0 to 6 should be deleted
for i in range(7):
assert not data[i].exists()
# While 7 and 8 remain
assert data[7].exists()
assert data[8].exists()
# So does folder X, which is invalid
storage._legacyDataFolder(fncPath, data[16])
assert data[16].exists()
# Fail cleanup of folder 9
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.rename", causeOSError)
mp.setattr("pathlib.Path.unlink", causeOSError)
storage._legacyDataFolder(fncPath, data[9])
assert data[9].exists()
assert not (fncPath / "content" / "9000000000009.nwd").exists()
# Run the remaining through the prepare storage call
assert storage._prepareStorage(checkLegacy=True) is True
for c in "0123456789abcdef":
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
# Deprecated Files
# ================
remove = [
fncPath / "meta" / "mainOptions.json",
fncPath / "meta" / "exportOptions.json",
fncPath / "meta" / "outlineOptions.json",
fncPath / "meta" / "timelineOptions.json",
fncPath / "meta" / "docMergeOptions.json",
fncPath / "meta" / "sessionLogOptions.json",
fncPath / "ToC.json",
]
for depFile in remove:
depFile.write_text("foo")
assert depFile.exists()
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.unlink", causeOSError)
storage._deleteDeprecatedFiles(fncPath)
for depFile in remove:
assert depFile.exists()
storage._deleteDeprecatedFiles(fncPath)
for depFile in remove:
assert not depFile.exists()
# END Test testCoreStorage_PrepareStorage
@pytest.mark.core
def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tmpPath, mockRnd):
"""Test making a zip archive of a project.
"""
zipFile = tmpPath / "project.zip"
theProject = NWProject(mockGUI)
storage = theProject.storage
assert storage.zipIt(zipFile) is False
# Make a project
mockRnd.reset()
buildTestProject(theProject, fncPath)
# Fail to create archive
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.ZipFile.write", causeOSError)
assert storage.zipIt(zipFile) is False
# Create archive
assert storage.zipIt(zipFile) is True
# Check content
with ZipFile(zipFile, mode="r") as archive:
names = archive.namelist()
assert names[0] == nwFiles.PROJ_FILE
assert names[1] == f"meta/{nwFiles.OPTS_FILE}"
assert names[2] == f"meta/{nwFiles.INDEX_FILE}"
assert names[3] == f"content/{C.hTitlePage}.nwd"
assert names[4] == f"content/{C.hChapterDoc}.nwd"
assert names[5] == f"content/{C.hSceneDoc}.nwd"
theProject.closeProject()
# END Test testCoreStorage_ZipIt