From 62cae915d0033daadc020a7abbc67069f6fc85ec Mon Sep 17 00:00:00 2001 From: suoto Date: Sat, 11 Mar 2017 00:32:24 +0000 Subject: [PATCH] Fixing some issues with as-you-type checks --- hdlcc/builders/base_builder.py | 2 +- hdlcc/handlers.py | 8 ++- hdlcc/hdlcc_base.py | 5 +- hdlcc/parsers/base_parser.py | 92 ++++++++++++++++++++--------- hdlcc/tests/test_hdlcc_base.py | 2 + hdlcc/tests/test_server_handlers.py | 58 +++++++++--------- 6 files changed, 104 insertions(+), 63 deletions(-) diff --git a/hdlcc/builders/base_builder.py b/hdlcc/builders/base_builder.py index 7067336..19e7209 100644 --- a/hdlcc/builders/base_builder.py +++ b/hdlcc/builders/base_builder.py @@ -276,7 +276,7 @@ def _buildAndParse(self, source, flags=None): # content, so we dump the buffer content to a temporary file # and tell the compiler to compile it instead if source.hasBufferContent(): - build_path = source.dumpBufferContentToFile() + build_path = source.getDumpPath() self._logger.debug("Source has buffered content, using %s", build_path) else: diff --git a/hdlcc/handlers.py b/hdlcc/handlers.py index 863939c..bcc1506 100644 --- a/hdlcc/handlers.py +++ b/hdlcc/handlers.py @@ -168,7 +168,13 @@ def getMessagesByPath(): "no content" if content is None else "with content") server = _getServerByProjectFile(project_file) - return {'messages': server.getMessagesByPath(path)} + response = {} + if content is None: + response['messages'] = server.getMessagesByPath(path) + else: + response['messages'] = server.getMessagesWithText(path, content) + + return response @app.post('/get_ui_messages') @_exceptionWrapper diff --git a/hdlcc/hdlcc_base.py b/hdlcc/hdlcc_base.py index 7aa2e92..bb4f039 100644 --- a/hdlcc/hdlcc_base.py +++ b/hdlcc/hdlcc_base.py @@ -483,9 +483,8 @@ def getMessagesWithText(self, path, content): self._setupEnvIfNeeded() source, remarks = self.getSourceByPath(path) - source.setBufferContent(content) - messages = self.getMessagesBySource(source) - source.clearBufferContent() + with source.havingBufferContent(content): + messages = self.getMessagesBySource(source) # Some messages may not include the filename field when checking a # file by content. In this case, we'll assume the empty filenames diff --git a/hdlcc/parsers/base_parser.py b/hdlcc/parsers/base_parser.py index 630d551..a09bcf8 100644 --- a/hdlcc/parsers/base_parser.py +++ b/hdlcc/parsers/base_parser.py @@ -17,10 +17,12 @@ "Base source file parser" import abc +import os import os.path as p import logging import re import time +from contextlib import contextmanager from hdlcc.utils import getFileType, removeDuplicates @@ -49,10 +51,7 @@ def __init__(self, filename, library='work', flags=None): self._content = None self._mtime = 0 self.filetype = getFileType(self.filename) - - self._buffer_time = None - self._buffer_content = None - + self._prev = None self.abspath = p.abspath(filename) def getState(self): @@ -83,8 +82,7 @@ def recoverFromState(cls, state): obj.flags = state['flags'] obj._cache = state['_cache'] obj._content = None - obj._buffer_time = None - obj._buffer_content = None + obj._prev = None obj._mtime = state['_mtime'] obj.filetype = state['filetype'] # pylint: enable=protected-access @@ -123,7 +121,10 @@ def _changed(self): Checks if the file changed based on the modification time provided by p.getmtime """ - return self.getmtime() > self._mtime + if self.getmtime() > self._mtime: + _logger.debug("File '%s' has changed", self.filename) + return True + return False def _clearCachesIfChanged(self): """ @@ -131,45 +132,77 @@ def _clearCachesIfChanged(self): every parsed info """ if self._changed(): - self._content = None + # Since the content was set by the caller, we can't really clear + # this unless we're handling with a proper file + if not self.hasBufferContent(): # pragma: no cover + self._content = None self._cache = {} def getmtime(self): """ Gets file modification time as defined in p.getmtime """ - if self._buffer_time is not None: - return self._buffer_time + if self.hasBufferContent(): + return 0 if not p.exists(self.filename): return None return p.getmtime(self.filename) - def setBufferContent(self, content): - self._buffer_content = content - self._buffer_time = time.time() + @contextmanager + def havingBufferContent(self, content): + """ + Context manager for handling a source file with a custom content + that is different from the file it points to. This is intended to + allow as-you-type checking + """ + self._setBufferContent(content) + yield + self._clearBufferContent() + + def getDumpPath(self): + """ + Returns the dump path in use while inside the havingBufferContent + context + """ + return p.join(p.dirname(self.filename), '.dump_' + + p.basename(self.filename)) def hasBufferContent(self): - return self._buffer_content is not None + """ + Returns true whenever the source is inside the havingBufferContent + context + """ + return self._prev is not None - def dumpBufferContentToFile(self): - buffer_dump_path = p.join(p.dirname(self.filename), '.dump_' + - p.basename(self.filename)) - open(buffer_dump_path, 'w').write(self._buffer_content) - return buffer_dump_path + def _setBufferContent(self, content): + """ + Setup portion of the havingBufferContent context + """ + _logger.debug("Setting source content") + self._prev = (self._mtime, self._content) + self._content = content + self._mtime = time.time() + + buffer_dump_path = self.getDumpPath() + _logger.debug("Dumping buffer content to '%s'", buffer_dump_path) + open(buffer_dump_path, 'w').write(self._content) + + def _clearBufferContent(self): + """ + Tear down portion of the havingBufferContent context + """ + _logger.debug("Clearing buffer content") + buffer_dump_path = self.getDumpPath() + if p.exists(buffer_dump_path): + os.remove(buffer_dump_path) - def clearBufferContent(self): - self._buffer_time = None - self._buffer_content = None + self._mtime, self._content = self._prev + self._prev = None def getSourceContent(self): """ Cached version of the _getSourceContent method """ - if self._buffer_content is not None: - if self._changed(): - self._cache = {} - return self._buffer_content - self._clearCachesIfChanged() if self._content is None: @@ -185,8 +218,9 @@ def getRawSourceContent(self): """ self._clearCachesIfChanged() - if self._buffer_content is not None: - return self._buffer_content + if self.hasBufferContent(): + return self._content + if 'raw_content' not in self._cache or self._changed(): self._cache['raw_content'] = \ open(self.filename, mode='rb').read().decode(errors='ignore') diff --git a/hdlcc/tests/test_hdlcc_base.py b/hdlcc/tests/test_hdlcc_base.py index 795f9c5..2e5069c 100644 --- a/hdlcc/tests/test_hdlcc_base.py +++ b/hdlcc/tests/test_hdlcc_base.py @@ -606,6 +606,8 @@ def test005b(): _logger.debug("Records received:") for record in records: _logger.debug("- %s", record) + else: + _logger.warning("No records found") # Check that all records point to the original filename and # remove them from the records so it's easier to compare diff --git a/hdlcc/tests/test_server_handlers.py b/hdlcc/tests/test_server_handlers.py index 4cef1d8..39bc04b 100644 --- a/hdlcc/tests/test_server_handlers.py +++ b/hdlcc/tests/test_server_handlers.py @@ -355,35 +355,35 @@ def build_with_buffer_leave(): # TODO: This test has side effects and makes other tests fail. Skip # it for now - # @it.should("get messages with content") - # def test(): - # data = { - # 'project_file' : it.PROJECT_FILE, - # 'path' : p.join( - # VIM_HDL_EXAMPLES, 'another_library', 'foo.vhd'), - # 'content' : '-- TODO: Nothing to see here'} - - # ui_reply = it.app.post('/get_ui_messages', data) - # reply = it.app.post('/get_messages_by_path', data) - - # _logger.info("UI reply: %s", ui_reply) - # _logger.info("Reply: %s", reply) - - # messages = reply.json['messages'] - - # for message in messages: - # it.assertTrue(utils.samefile(message.pop('filename'), - # data['path'])) - - # it.assertIn( - # {"error_type" : "W", - # "checker" : "HDL Code Checker/static", - # "line_number" : 1, - # "column" : 4, - # "error_subtype" : "", - # "error_number" : "0", - # "error_message" : "TODO: Nothing to see here"}, - # messages) + @it.should("get messages with content") + def test(): + data = { + 'project_file' : it.PROJECT_FILE, + 'path' : p.join( + VIM_HDL_EXAMPLES, 'another_library', 'foo.vhd'), + 'content' : '-- TODO: Nothing to see here'} + + ui_reply = it.app.post('/get_ui_messages', data) + reply = it.app.post('/get_messages_by_path', data) + + _logger.info("UI reply: %s", ui_reply) + _logger.info("Reply: %s", reply) + + messages = reply.json['messages'] + + for message in messages: + it.assertTrue(utils.samefile(message.pop('filename'), + data['path'])) + + it.assertIn( + {"error_type" : "W", + "checker" : "HDL Code Checker/static", + "line_number" : 1, + "column" : 4, + "error_subtype" : "", + "error_number" : "0", + "error_message" : "TODO: Nothing to see here"}, + messages) @it.should("get source dependencies") @mock.patch('hdlcc.config_parser.foundVunit', lambda: False)