Update tests for storage class
This commit is contained in:
@@ -85,7 +85,6 @@ class NWStorage:
|
|||||||
self._lockFilePath = None
|
self._lockFilePath = None
|
||||||
self._openMode = self.MODE_INACTIVE
|
self._openMode = self.MODE_INACTIVE
|
||||||
self._ready = False
|
self._ready = False
|
||||||
self._exception = None
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -154,6 +153,7 @@ class NWStorage:
|
|||||||
metaPath.mkdir(exist_ok=True)
|
metaPath.mkdir(exist_ok=True)
|
||||||
contPath.mkdir(exist_ok=True)
|
contPath.mkdir(exist_ok=True)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
self._exception = exc
|
||||||
logger.error("Failed to create project folders", exc_info=exc)
|
logger.error("Failed to create project folders", exc_info=exc)
|
||||||
self.clear()
|
self.clear()
|
||||||
return False
|
return False
|
||||||
@@ -199,11 +199,8 @@ class NWStorage:
|
|||||||
self._clearLockFile()
|
self._clearLockFile()
|
||||||
|
|
||||||
self._readLockFile()
|
self._readLockFile()
|
||||||
if self._lockedBy and len(self._lockedBy) == 4:
|
if self._lockedBy:
|
||||||
if self._lockedBy[0] == "ERROR":
|
logger.error("Project is locked, so not opening")
|
||||||
logger.warning("Failed to check lock file")
|
|
||||||
else:
|
|
||||||
logger.error("Project is locked, so not opening")
|
|
||||||
return NWStorageOpen.LOCKED
|
return NWStorageOpen.LOCKED
|
||||||
else:
|
else:
|
||||||
logger.debug("Project is not locked")
|
logger.debug("Project is not locked")
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from tools import C, buildTestProject, writeFile
|
from tools import C, buildTestProject
|
||||||
from mocked import causeOSError
|
from mocked import causeOSError
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.constants import nwFiles
|
from novelwriter.constants import nwFiles
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.core.storage import NWStorage, _LegacyStorage
|
from novelwriter.core.storage import NWStorage, NWStorageOpen, _LegacyStorage
|
||||||
|
from novelwriter.core.document import NWDocument
|
||||||
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
|
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
|
||||||
|
|
||||||
|
|
||||||
@@ -42,21 +43,19 @@ class MockProject:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
|
def testCoreStorage_CreateNewProject(mockGUI, fncPath):
|
||||||
"""Test opening a project in a folder."""
|
"""Test creating a project in a folder."""
|
||||||
theProject = NWProject()
|
project = NWProject()
|
||||||
mockRnd.reset()
|
|
||||||
buildTestProject(theProject, fncPath)
|
|
||||||
theProject.closeProject()
|
|
||||||
|
|
||||||
# Create instance
|
# Create instance
|
||||||
storage = NWStorage(theProject)
|
storage = NWStorage(project)
|
||||||
|
|
||||||
# Check defaults
|
# Check defaults
|
||||||
assert storage.storagePath is None
|
assert storage.storagePath is None
|
||||||
assert storage.runtimePath is None
|
assert storage.runtimePath is None
|
||||||
assert storage.contentPath is None
|
assert storage.contentPath is None
|
||||||
assert storage._openMode == NWStorage.MODE_INACTIVE
|
assert storage._openMode == NWStorage.MODE_INACTIVE
|
||||||
|
assert storage._ready is False
|
||||||
|
|
||||||
# Check closed project return values
|
# Check closed project return values
|
||||||
assert storage.isOpen() is False
|
assert storage.isOpen() is False
|
||||||
@@ -66,52 +65,137 @@ def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
|
|||||||
assert storage.getMetaFile("file") is None
|
assert storage.getMetaFile("file") is None
|
||||||
assert storage.scanContent() == []
|
assert storage.scanContent() == []
|
||||||
|
|
||||||
# Open project as a new project should fail
|
# Cannot prepare a non-empty folder
|
||||||
assert storage.initProjectStorage(fncPath, newProject=True) is False
|
(fncPath / "foobar.txt").touch()
|
||||||
|
assert storage.createNewProject(fncPath) is False
|
||||||
|
|
||||||
# Opening as a non-new project is fine
|
# Try creating in a non-existent subfolder instead
|
||||||
assert storage.initProjectStorage(fncPath, newProject=False) is True
|
assert storage.createNewProject(fncPath / "project1") is True
|
||||||
|
assert (fncPath / "project1").is_dir()
|
||||||
|
assert (fncPath / "project1" / "meta").is_dir()
|
||||||
|
assert (fncPath / "project1" / "content").is_dir()
|
||||||
|
|
||||||
# Opening the project file is also fine
|
# However, the parent folder must exist
|
||||||
assert storage.initProjectStorage(fncPath / nwFiles.PROJ_FILE, newProject=False) is True
|
assert storage.createNewProject(fncPath / "foobar" / "project1") is False
|
||||||
|
assert isinstance(storage.exc, FileNotFoundError)
|
||||||
|
|
||||||
# Opening as a non-new project on a non-existing folder should fail
|
project.closeProject()
|
||||||
assert storage.initProjectStorage(fncPath / "foobar", newProject=False) is False
|
|
||||||
|
|
||||||
# Check settings
|
# END Test testCoreStorage_CreateNewProject
|
||||||
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
|
@pytest.mark.core
|
||||||
assert isinstance(storage.getXmlReader(), ProjectXMLReader)
|
def testCoreStorage_InitProjectStorage(mockGUI, fncPath, mockRnd):
|
||||||
assert isinstance(storage.getXmlWriter(), ProjectXMLWriter)
|
"""Test initialising a project in a folder."""
|
||||||
|
project = NWProject()
|
||||||
|
|
||||||
# Get content
|
# Create instance
|
||||||
assert sorted(storage.scanContent()) == [C.hTitlePage, C.hChapterDoc, C.hSceneDoc]
|
storage = NWStorage(project)
|
||||||
|
|
||||||
# Get document
|
# Check defaults
|
||||||
assert storage.getDocument(C.hSceneDoc).readDocument() == "### New Scene\n\n"
|
assert storage.storagePath is None
|
||||||
|
assert storage.runtimePath is None
|
||||||
|
assert storage.contentPath is None
|
||||||
|
assert storage._openMode == NWStorage.MODE_INACTIVE
|
||||||
|
assert storage._ready is False
|
||||||
|
|
||||||
# Get paths
|
# Check closed project return values
|
||||||
assert storage.getMetaFile("stuff") == fncPath / "meta" / "stuff"
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
theProject.closeProject()
|
|
||||||
|
|
||||||
# Check closed project return values (again)
|
|
||||||
assert storage.isOpen() is False
|
assert storage.isOpen() is False
|
||||||
assert storage.getXmlReader() is None
|
assert storage.getXmlReader() is None
|
||||||
assert storage.getXmlWriter() is None
|
assert storage.getXmlWriter() is None
|
||||||
assert bool(storage.getDocument(C.hSceneDoc)) is False
|
assert bool(storage.getDocument(C.hSceneDoc)) is False
|
||||||
assert storage.getMetaFile("file") is None
|
assert storage.getMetaFile("file") is None
|
||||||
|
assert storage.scanContent() == []
|
||||||
|
|
||||||
# END Test testCoreStorage_ProjectInPlace
|
# Create a new project
|
||||||
|
buildTestProject(project, fncPath)
|
||||||
|
|
||||||
|
# Init with the wrong file
|
||||||
|
assert storage.initProjectStorage(fncPath / "foobar.txt") == NWStorageOpen.UNKOWN
|
||||||
|
storage._clearLockFile()
|
||||||
|
storage.clear()
|
||||||
|
|
||||||
|
# Init with the user's home dir
|
||||||
|
assert storage.initProjectStorage(Path.home()) == NWStorageOpen.UNKOWN
|
||||||
|
storage._clearLockFile()
|
||||||
|
storage.clear()
|
||||||
|
|
||||||
|
# Init with the project folder is OK
|
||||||
|
assert storage.initProjectStorage(fncPath) == NWStorageOpen.READY
|
||||||
|
assert storage.runtimePath == fncPath
|
||||||
|
assert storage.storagePath == fncPath
|
||||||
|
assert storage.contentPath == fncPath / "content"
|
||||||
|
assert storage._openMode == NWStorage.MODE_INPLACE
|
||||||
|
storage._clearLockFile()
|
||||||
|
storage.clear()
|
||||||
|
|
||||||
|
# Init with the project main file is OK
|
||||||
|
assert storage.initProjectStorage(fncPath / nwFiles.PROJ_FILE) == NWStorageOpen.READY
|
||||||
|
assert storage.runtimePath == fncPath
|
||||||
|
assert storage.storagePath == fncPath
|
||||||
|
assert storage.contentPath == fncPath / "content"
|
||||||
|
assert storage._openMode == NWStorage.MODE_INPLACE
|
||||||
|
storage._clearLockFile()
|
||||||
|
storage.clear()
|
||||||
|
|
||||||
|
# Open twice, where second should fail due to lockfile
|
||||||
|
assert storage.initProjectStorage(fncPath) == NWStorageOpen.READY
|
||||||
|
assert storage.initProjectStorage(fncPath) == NWStorageOpen.LOCKED
|
||||||
|
assert isinstance(storage.lockStatus, list)
|
||||||
|
assert len(storage.lockStatus) == 4
|
||||||
|
|
||||||
|
# But open again with clear lock file flag set is OK
|
||||||
|
assert storage.initProjectStorage(fncPath, clearLock=True) == NWStorageOpen.READY
|
||||||
|
assert storage.lockStatus is None
|
||||||
|
|
||||||
|
# We should now have access to project resources
|
||||||
|
assert isinstance(storage.getXmlReader(), ProjectXMLReader)
|
||||||
|
assert isinstance(storage.getXmlWriter(), ProjectXMLWriter)
|
||||||
|
assert isinstance(storage.getDocument(C.hSceneDoc), NWDocument)
|
||||||
|
assert repr(storage.getDocument(C.hSceneDoc)) == f"<NWDocument handle={C.hSceneDoc}>"
|
||||||
|
|
||||||
|
project.closeProject()
|
||||||
|
|
||||||
|
# END Test testCoreStorage_InitProjectStorage
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreStorage_InitProjectStorage_Invalid(mockGUI, fncPath):
|
||||||
|
"""Test initialising a project in an invalid folder."""
|
||||||
|
project = NWProject()
|
||||||
|
|
||||||
|
# Create instance
|
||||||
|
storage = NWStorage(project)
|
||||||
|
|
||||||
|
# Check defaults
|
||||||
|
assert storage.storagePath is None
|
||||||
|
assert storage.runtimePath is None
|
||||||
|
assert storage.contentPath is None
|
||||||
|
assert storage._openMode == NWStorage.MODE_INACTIVE
|
||||||
|
assert storage._ready is False
|
||||||
|
|
||||||
|
# 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.scanContent() == []
|
||||||
|
|
||||||
|
# Populate folder with invalid files
|
||||||
|
(fncPath / "meta").touch() # These are now files but should be folders
|
||||||
|
(fncPath / "content").touch() # These are now files but should be folders
|
||||||
|
|
||||||
|
# Try opening the folder, but there is no project file
|
||||||
|
assert storage.initProjectStorage(fncPath) == NWStorageOpen.NOT_FOUND
|
||||||
|
|
||||||
|
# Add the project file, and we should now fail on the folders
|
||||||
|
(fncPath / nwFiles.PROJ_FILE).touch()
|
||||||
|
assert storage.initProjectStorage(fncPath) == NWStorageOpen.FAILED
|
||||||
|
|
||||||
|
project.closeProject()
|
||||||
|
|
||||||
|
# END Test testCoreStorage_InitProjectStorage_Invalid
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
@@ -123,7 +207,7 @@ def testCoreStorage_LockFile(monkeypatch, fncPath):
|
|||||||
assert storage.isOpen() is False
|
assert storage.isOpen() is False
|
||||||
|
|
||||||
# Project not open, so cannot read/write lock file
|
# Project not open, so cannot read/write lock file
|
||||||
assert storage._readLockFile() == ["ERROR"]
|
assert storage._readLockFile() is None
|
||||||
assert storage._writeLockFile() is False
|
assert storage._writeLockFile() is False
|
||||||
assert storage._clearLockFile() is False
|
assert storage._clearLockFile() is False
|
||||||
|
|
||||||
@@ -132,7 +216,8 @@ def testCoreStorage_LockFile(monkeypatch, fncPath):
|
|||||||
storage._lockFilePath = lockFilePath
|
storage._lockFilePath = lockFilePath
|
||||||
|
|
||||||
# Path is set, but there is no lockfile
|
# Path is set, but there is no lockfile
|
||||||
assert storage._readLockFile() == []
|
storage._readLockFile()
|
||||||
|
assert storage.lockStatus is None
|
||||||
|
|
||||||
# Write lockfile fails
|
# Write lockfile fails
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
@@ -148,20 +233,20 @@ def testCoreStorage_LockFile(monkeypatch, fncPath):
|
|||||||
# Read lockfile fails
|
# Read lockfile fails
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr("pathlib.Path.read_text", causeOSError)
|
mp.setattr("pathlib.Path.read_text", causeOSError)
|
||||||
assert storage._readLockFile() == ["ERROR"]
|
storage._readLockFile()
|
||||||
|
assert storage.lockStatus == ["ERROR", "ERROR", "ERROR", "ERROR"]
|
||||||
assert lockFilePath.exists()
|
assert lockFilePath.exists()
|
||||||
|
|
||||||
# Successful read
|
# Successful read
|
||||||
assert storage._readLockFile() == [
|
storage._readLockFile()
|
||||||
CONFIG.hostName,
|
assert storage.lockStatus == [
|
||||||
CONFIG.osType,
|
CONFIG.hostName, CONFIG.osType, CONFIG.kernelVer, "1000",
|
||||||
CONFIG.kernelVer,
|
|
||||||
"1000",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Write an invalid lockfile
|
# Write an invalid lockfile
|
||||||
writeFile(lockFilePath, "a;b;c")
|
lockFilePath.write_text("a;b;c")
|
||||||
assert storage._readLockFile() == ["ERROR"]
|
storage._readLockFile()
|
||||||
|
assert storage.lockStatus is None
|
||||||
|
|
||||||
# Fail to remove lockfile
|
# Fail to remove lockfile
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
@@ -212,46 +297,46 @@ def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd):
|
|||||||
# END Test testCoreStorage_ZipIt
|
# END Test testCoreStorage_ZipIt
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
# @pytest.mark.core
|
||||||
def testCoreStorage_PrepareStorage(monkeypatch, fncPath):
|
# def testCoreStorage_PrepareStorage(monkeypatch, fncPath):
|
||||||
"""Test the project path preparation functions."""
|
# """Test the project path preparation functions."""
|
||||||
storage = NWStorage(MockProject()) # type: ignore
|
# storage = NWStorage(MockProject()) # type: ignore
|
||||||
assert storage.isOpen() is False
|
# assert storage.isOpen() is False
|
||||||
|
|
||||||
# No path set
|
# # No path set
|
||||||
assert storage._prepareStorage() is False
|
# assert storage._prepareStorage() is False
|
||||||
|
|
||||||
# Set path to home
|
# # Set path to home
|
||||||
storage._runtimePath = fncPath
|
# storage._runtimePath = fncPath
|
||||||
with monkeypatch.context() as mp:
|
# with monkeypatch.context() as mp:
|
||||||
mp.setattr("pathlib.Path.home", lambda: fncPath)
|
# mp.setattr("pathlib.Path.home", lambda: fncPath)
|
||||||
assert storage._prepareStorage() is False
|
# assert storage._prepareStorage() is False
|
||||||
|
|
||||||
# Fail on mkdir
|
# # Fail on mkdir
|
||||||
storage._runtimePath = fncPath
|
# storage._runtimePath = fncPath
|
||||||
with monkeypatch.context() as mp:
|
# with monkeypatch.context() as mp:
|
||||||
mp.setattr("pathlib.Path.mkdir", causeOSError)
|
# mp.setattr("pathlib.Path.mkdir", causeOSError)
|
||||||
assert storage._prepareStorage() is False
|
# assert storage._prepareStorage() is False
|
||||||
|
|
||||||
# Set up the folder
|
# # Set up the folder
|
||||||
storage._runtimePath = fncPath
|
# storage._runtimePath = fncPath
|
||||||
assert storage._prepareStorage(checkLegacy=False) is True
|
# assert storage._prepareStorage(checkLegacy=False) is True
|
||||||
assert (fncPath / "content").exists()
|
# assert (fncPath / "content").exists()
|
||||||
assert (fncPath / "meta").exists()
|
# assert (fncPath / "meta").exists()
|
||||||
assert not (fncPath / "cache").exists() # Removed in 2.1b1
|
# assert not (fncPath / "cache").exists() # Removed in 2.1b1
|
||||||
|
|
||||||
# Add a legacy folder
|
# # Add a legacy folder
|
||||||
storage._runtimePath = fncPath
|
# storage._runtimePath = fncPath
|
||||||
dataDir = fncPath / "data_0"
|
# dataDir = fncPath / "data_0"
|
||||||
dataDir.mkdir()
|
# dataDir.mkdir()
|
||||||
assert storage._prepareStorage(checkLegacy=True) is True
|
# assert storage._prepareStorage(checkLegacy=True) is True
|
||||||
assert not dataDir.exists()
|
# assert not dataDir.exists()
|
||||||
|
|
||||||
# We cannot add a new project here
|
# # We cannot add a new project here
|
||||||
storage._runtimePath = fncPath
|
# storage._runtimePath = fncPath
|
||||||
assert storage._prepareStorage(checkLegacy=False, newProject=True) is False
|
# assert storage._prepareStorage(checkLegacy=False, newProject=True) is False
|
||||||
|
|
||||||
# END Test testCoreStorage_PrepareStorage
|
# # END Test testCoreStorage_PrepareStorage
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
@@ -261,7 +346,8 @@ def testCoreStorage_LegacyDataFolder(monkeypatch, fncPath):
|
|||||||
storage = NWStorage(project) # type: ignore
|
storage = NWStorage(project) # type: ignore
|
||||||
assert storage.isOpen() is False
|
assert storage.isOpen() is False
|
||||||
storage._runtimePath = fncPath
|
storage._runtimePath = fncPath
|
||||||
assert storage._prepareStorage() is True
|
(fncPath / nwFiles.PROJ_FILE).touch()
|
||||||
|
storage.initProjectStorage(fncPath)
|
||||||
legacy = _LegacyStorage(project) # type: ignore
|
legacy = _LegacyStorage(project) # type: ignore
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@@ -290,8 +376,8 @@ def testCoreStorage_LegacyDataFolder(monkeypatch, fncPath):
|
|||||||
legacy.legacyDataFolder(fncPath, data[i])
|
legacy.legacyDataFolder(fncPath, data[i])
|
||||||
|
|
||||||
# Files form 0 to 8 should now be in content
|
# Files form 0 to 8 should now be in content
|
||||||
for c in "012345678":
|
for i in range(9):
|
||||||
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
|
assert (fncPath / "content" / f"{i}00000000000{i}.nwd").exists()
|
||||||
|
|
||||||
# Folders 0 to 6 should be deleted
|
# Folders 0 to 6 should be deleted
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
@@ -314,7 +400,7 @@ def testCoreStorage_LegacyDataFolder(monkeypatch, fncPath):
|
|||||||
assert not (fncPath / "content" / "9000000000009.nwd").exists()
|
assert not (fncPath / "content" / "9000000000009.nwd").exists()
|
||||||
|
|
||||||
# Run the remaining through the prepare storage call
|
# Run the remaining through the prepare storage call
|
||||||
assert storage._prepareStorage(checkLegacy=True) is True
|
assert storage.initProjectStorage(fncPath, clearLock=True) == NWStorageOpen.READY
|
||||||
for c in "0123456789abcdef":
|
for c in "0123456789abcdef":
|
||||||
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
|
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
|
||||||
|
|
||||||
@@ -328,7 +414,8 @@ def testCoreStorage_DeprecatedFiles(monkeypatch, fncPath):
|
|||||||
storage = NWStorage(project) # type: ignore
|
storage = NWStorage(project) # type: ignore
|
||||||
assert storage.isOpen() is False
|
assert storage.isOpen() is False
|
||||||
storage._runtimePath = fncPath
|
storage._runtimePath = fncPath
|
||||||
assert storage._prepareStorage() is True
|
(fncPath / nwFiles.PROJ_FILE).touch()
|
||||||
|
storage.initProjectStorage(fncPath)
|
||||||
legacy = _LegacyStorage(project) # type: ignore
|
legacy = _LegacyStorage(project) # type: ignore
|
||||||
|
|
||||||
# Files/Folders to be Deleted or Renamed
|
# Files/Folders to be Deleted or Renamed
|
||||||
|
|||||||
Reference in New Issue
Block a user