From 86d5a6f6b2041b1181d14ed635e918d3a91a9bd0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 30 Sep 2020 22:57:00 +0200 Subject: [PATCH] Coverage of index class 100% --- nw/core/index.py | 21 +- tests/reference/proj/1_tagsIndex.json | 560 ++++++++++++++++++++++++++ tests/test_index.py | 132 ++++++ 3 files changed, 702 insertions(+), 11 deletions(-) create mode 100644 tests/reference/proj/1_tagsIndex.json diff --git a/nw/core/index.py b/nw/core/index.py index 8925b07e..13c6c092 100644 --- a/nw/core/index.py +++ b/nw/core/index.py @@ -29,8 +29,7 @@ import nw import logging import json import os - -from time import time +import time from nw.constants import ( nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert @@ -177,7 +176,7 @@ class NWIndex(): if "textCounts" in theData.keys(): self.textCounts = theData["textCounts"] - nowTime = round(time()) + nowTime = round(time.time()) self.timeNovel = nowTime self.timeNote = nowTime self.timeIndex = nowTime @@ -305,7 +304,7 @@ class NWIndex(): self.refIndex[tHandle] = {} self.refIndex[tHandle]["T000000"] = { "tags" : [], - "updated" : round(time()), + "updated" : round(time.time()), } if itemLayout == nwItemLayout.NOTE: self.noteIndex[tHandle] = {} @@ -360,7 +359,7 @@ class NWIndex(): self._indexWordCounts(tHandle, isNovel, lastText, nTitle) # Update timestamps for index changes - nowTime = round(time()) + nowTime = round(time.time()) self.timeIndex = nowTime if isNovel: self.timeNovel = nowTime @@ -395,7 +394,7 @@ class NWIndex(): sTitle = "T%06d" % nLine self.refIndex[tHandle][sTitle] = { "tags" : [], - "updated" : round(time()), + "updated" : round(time.time()), } theData = { "level" : hDepth, @@ -405,7 +404,7 @@ class NWIndex(): "cCount" : 0, "wCount" : 0, "pCount" : 0, - "updated" : round(time()), + "updated" : round(time.time()), } if hText != "": @@ -429,14 +428,14 @@ class NWIndex(): self.novelIndex[tHandle][sTitle]["cCount"] = cC self.novelIndex[tHandle][sTitle]["wCount"] = wC self.novelIndex[tHandle][sTitle]["pCount"] = pC - self.novelIndex[tHandle][sTitle]["updated"] = round(time()) + self.novelIndex[tHandle][sTitle]["updated"] = round(time.time()) else: if tHandle in self.noteIndex: if sTitle in self.noteIndex[tHandle]: self.noteIndex[tHandle][sTitle]["cCount"] = cC self.noteIndex[tHandle][sTitle]["wCount"] = wC self.noteIndex[tHandle][sTitle]["pCount"] = pC - self.noteIndex[tHandle][sTitle]["updated"] = round(time()) + self.noteIndex[tHandle][sTitle]["updated"] = round(time.time()) return def _indexSynopsis(self, tHandle, isNovel, theText, nTitle): @@ -447,12 +446,12 @@ class NWIndex(): if tHandle in self.novelIndex: if sTitle in self.novelIndex[tHandle]: self.novelIndex[tHandle][sTitle]["synopsis"] = theText - self.novelIndex[tHandle][sTitle]["updated"] = round(time()) + self.novelIndex[tHandle][sTitle]["updated"] = round(time.time()) else: if tHandle in self.noteIndex: if sTitle in self.noteIndex[tHandle]: self.noteIndex[tHandle][sTitle]["synopsis"] = theText - self.noteIndex[tHandle][sTitle]["updated"] = round(time()) + self.noteIndex[tHandle][sTitle]["updated"] = round(time.time()) return def _indexNoteRef(self, tHandle, aLine, nLine, nTitle): diff --git a/tests/reference/proj/1_tagsIndex.json b/tests/reference/proj/1_tagsIndex.json new file mode 100644 index 00000000..2bb3b44a --- /dev/null +++ b/tests/reference/proj/1_tagsIndex.json @@ -0,0 +1,560 @@ +{ + "tagIndex": { + "Bod": [ + 3, + "4c4f28287af27", + "CHARACTER", + "T000001" + ], + "Main": [ + 3, + "2426c6f0ca922", + "PLOT", + "T000001" + ], + "Europe": [ + 3, + "04468803b92e1", + "WORLD", + "T000001" + ] + }, + "refIndex": { + "7a992350f3eb6": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "8c58a65414c23": { + "T000000": { + "tags": [], + "updated": 123 + } + }, + "88d59a277361b": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "db7e733775d4d": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "fb609cd8319dc": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + } + }, + "88243afbe5ed8": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + }, + "T000013": { + "tags": [], + "updated": 123 + } + }, + "f96ec11c6a3da": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + }, + "T000015": { + "tags": [], + "updated": 123 + } + }, + "846352075de7d": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "441420a886d82": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + } + }, + "eb103bc70c90c": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + } + }, + "f8c0562e50f1b": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + } + }, + "47666c91c7ccf": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [ + [ + 3, + "@pov", + "Bod" + ], + [ + 4, + "@plot", + "Main" + ], + [ + 5, + "@location", + "Europe" + ] + ], + "updated": 123 + } + }, + "4c4f28287af27": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "2426c6f0ca922": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + }, + "04468803b92e1": { + "T000000": { + "tags": [], + "updated": 123 + }, + "T000001": { + "tags": [], + "updated": 123 + } + } + }, + "novelIndex": { + "7a992350f3eb6": { + "T000001": { + "level": "H1", + "title": "Lorem Ipsum", + "layout": "TITLE", + "synopsis": "", + "cCount": 230, + "wCount": 40, + "pCount": 3, + "updated": 123 + } + }, + "8c58a65414c23": {}, + "88d59a277361b": { + "T000001": { + "level": "H2", + "title": "Prologue", + "layout": "UNNUMBERED", + "synopsis": "Explanation from the lipsum.com website.", + "cCount": 584, + "wCount": 92, + "pCount": 1, + "updated": 123 + } + }, + "db7e733775d4d": { + "T000001": { + "level": "H1", + "title": "Act One", + "layout": "PARTITION", + "synopsis": "", + "cCount": 35, + "wCount": 6, + "pCount": 1, + "updated": 123 + } + }, + "fb609cd8319dc": { + "T000001": { + "level": "H2", + "title": "Chapter One", + "layout": "CHAPTER", + "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.", + "cCount": 419, + "wCount": 67, + "pCount": 1, + "updated": 123 + } + }, + "88243afbe5ed8": { + "T000001": { + "level": "H3", + "title": "Scene One", + "layout": "SCENE", + "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.", + "cCount": 1197, + "wCount": 174, + "pCount": 2, + "updated": 123 + }, + "T000013": { + "level": "H4", + "title": "Scene One, Section Two", + "layout": "SCENE", + "synopsis": "", + "cCount": 1561, + "wCount": 230, + "pCount": 2, + "updated": 123 + } + }, + "f96ec11c6a3da": { + "T000001": { + "level": "H3", + "title": "Scene Two", + "layout": "SCENE", + "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.", + "cCount": 2034, + "wCount": 299, + "pCount": 3, + "updated": 123 + }, + "T000015": { + "level": "H4", + "title": "Scene Two, Section Two", + "layout": "SCENE", + "synopsis": "", + "cCount": 2009, + "wCount": 301, + "pCount": 3, + "updated": 123 + } + }, + "846352075de7d": { + "T000001": { + "level": "H2", + "title": "Why do we use it?", + "layout": "BOOK", + "synopsis": "", + "cCount": 630, + "wCount": 109, + "pCount": 1, + "updated": 123 + } + }, + "441420a886d82": { + "T000001": { + "level": "H2", + "title": "Chapter Two", + "layout": "CHAPTER", + "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.", + "cCount": 477, + "wCount": 70, + "pCount": 1, + "updated": 123 + } + }, + "eb103bc70c90c": { + "T000001": { + "level": "H3", + "title": "Scene Three", + "layout": "SCENE", + "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.", + "cCount": 3006, + "wCount": 439, + "pCount": 4, + "updated": 123 + } + }, + "f8c0562e50f1b": { + "T000001": { + "level": "H3", + "title": "Scene Four", + "layout": "SCENE", + "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.", + "cCount": 3839, + "wCount": 563, + "pCount": 6, + "updated": 123 + } + }, + "47666c91c7ccf": { + "T000001": { + "level": "H3", + "title": "Scene Five", + "layout": "SCENE", + "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.", + "cCount": 3644, + "wCount": 543, + "pCount": 5, + "updated": 123 + } + } + }, + "noteIndex": { + "4c4f28287af27": { + "T000001": { + "level": "H1", + "title": "Nobody Owens", + "layout": "NOTE", + "synopsis": "", + "cCount": 1864, + "wCount": 284, + "pCount": 3, + "updated": 123 + } + }, + "2426c6f0ca922": { + "T000001": { + "level": "H1", + "title": "Main Plot", + "layout": "NOTE", + "synopsis": "", + "cCount": 1369, + "wCount": 195, + "pCount": 2, + "updated": 123 + } + }, + "04468803b92e1": { + "T000001": { + "level": "H1", + "title": "Ancient Europe", + "layout": "NOTE", + "synopsis": "", + "cCount": 1770, + "wCount": 259, + "pCount": 3, + "updated": 123 + } + } + }, + "textCounts": { + "7a992350f3eb6": [ + 230, + 40, + 3 + ], + "8c58a65414c23": [ + 1058, + 176, + 2 + ], + "88d59a277361b": [ + 584, + 92, + 1 + ], + "db7e733775d4d": [ + 35, + 6, + 1 + ], + "fb609cd8319dc": [ + 419, + 67, + 1 + ], + "88243afbe5ed8": [ + 2758, + 404, + 4 + ], + "f96ec11c6a3da": [ + 4043, + 600, + 6 + ], + "846352075de7d": [ + 630, + 109, + 1 + ], + "441420a886d82": [ + 477, + 70, + 1 + ], + "eb103bc70c90c": [ + 3006, + 439, + 4 + ], + "f8c0562e50f1b": [ + 3839, + 563, + 6 + ], + "47666c91c7ccf": [ + 3644, + 543, + 5 + ], + "4c4f28287af27": [ + 1864, + 284, + 3 + ], + "2426c6f0ca922": [ + 1369, + 195, + 2 + ], + "04468803b92e1": [ + 1770, + 259, + 3 + ] + } +} \ No newline at end of file diff --git a/tests/test_index.py b/tests/test_index.py index 084a9d39..11e6c595 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -3,11 +3,143 @@ """ import pytest +import time +import os +import json + +from shutil import copyfile + +from nwtools import cmpFiles from nw.core.project import NWProject from nw.core.index import NWIndex from nw.constants import nwItemClass, nwItemLayout +@pytest.mark.project +def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef): + """Test core functionality of scaning, saving, loading and checking + the index cache file. + """ + projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") + testFile = os.path.join(nwTempProj, "1_tagsIndex.json") + refFile = os.path.join(nwRef, "proj", "1_tagsIndex.json") + + theProject = NWProject(nwDummy) + theProject.projTree.setSeed(42) + assert theProject.openProject(nwLipsum) + + theProject.mainConf.debugInfo = True + monkeypatch.setattr(time, "time", lambda: 123.4) + + theIndex = NWIndex(theProject, nwDummy) + notIndexable = { + "b3643d0f92e32": False, # Novel ROOT + "45e6b01ca35c1": False, # Chapter One FOLDER + "6bd935d2490cd": False, # Chapter Two FOLDER + "67a8707f2f249": False, # Character ROOT + "6c6afb1247750": False, # Plot ROOT + "60bdf227455cc": False, # World ROOT + } + for tItem in theProject.projTree: + assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get(tItem.itemHandle, True) + + assert not theIndex.reIndexHandle(None) + + # Dummy exception function + def doPanic(*arg, **kwargs): + raise Exception + + # Make the save fail + monkeypatch.setattr(json, "dumps", doPanic) + assert not theIndex.saveIndex() + + # Make the save pass + monkeypatch.undo() + assert theIndex.saveIndex() + + # Take a copy of the index + tagIndex = str(theIndex.tagIndex) + refIndex = str(theIndex.refIndex) + novelIndex = str(theIndex.novelIndex) + noteIndex = str(theIndex.noteIndex) + textCounts = str(theIndex.textCounts) + + # Delete a handle + assert theIndex.tagIndex.get("Bod", None) is not None + assert theIndex.refIndex.get("4c4f28287af27", None) is not None + assert theIndex.noteIndex.get("4c4f28287af27", None) is not None + assert theIndex.textCounts.get("4c4f28287af27", None) is not None + theIndex.deleteHandle("4c4f28287af27") + assert theIndex.tagIndex.get("Bod", None) is None + assert theIndex.refIndex.get("4c4f28287af27", None) is None + assert theIndex.noteIndex.get("4c4f28287af27", None) is None + assert theIndex.textCounts.get("4c4f28287af27", None) is None + + # Clear the index + theIndex.clearIndex() + assert not theIndex.tagIndex + assert not theIndex.refIndex + assert not theIndex.novelIndex + assert not theIndex.noteIndex + assert not theIndex.textCounts + + # Make the load fail + monkeypatch.setattr(json, "loads", doPanic) + assert not theIndex.loadIndex() + + # Make the load pass + monkeypatch.undo() + assert theIndex.loadIndex() + + assert str(theIndex.tagIndex) == tagIndex + assert str(theIndex.refIndex) == refIndex + assert str(theIndex.novelIndex) == novelIndex + assert str(theIndex.noteIndex) == noteIndex + assert str(theIndex.textCounts) == textCounts + + # Break the index and check that we notice + assert not theIndex.indexBroken + theIndex.tagIndex["Bod"].append("Stuff") # No longer len() == 4 + theIndex.checkIndex() + assert theIndex.indexBroken + + assert theIndex.loadIndex() + assert not theIndex.indexBroken + theIndex.refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3 + theIndex.checkIndex() + assert theIndex.indexBroken + + assert theIndex.loadIndex() + assert not theIndex.indexBroken + theIndex.novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8 + theIndex.checkIndex() + assert theIndex.indexBroken + + assert theIndex.loadIndex() + assert not theIndex.indexBroken + theIndex.noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8 + theIndex.checkIndex() + assert theIndex.indexBroken + + assert theIndex.loadIndex() + assert not theIndex.indexBroken + theIndex.textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3 + theIndex.checkIndex() + assert theIndex.indexBroken + + # Make the try/except trigger as well + assert theIndex.loadIndex() + assert not theIndex.indexBroken + theIndex.refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name + theIndex.checkIndex() + assert theIndex.indexBroken + + # Finalise + assert theProject.closeProject() + + copyfile(projFile, testFile) + assert cmpFiles(testFile, refFile) + @pytest.mark.project def testIndexScanThis(nwMinimal, nwDummy): """Test the tag scanner function scanThis.