import unittest from unittest import mock from .utils import config_from_parts_or_dicts, normalize from dbt.contracts.graph.manifest import FileHash, FilePath, SourceFile from dbt.parser import ParseResult from dbt.parser.search import FileBlock from dbt.parser import manifest class MatchingHash(FileHash): def __init__(self): return super().__init__('', '') def __eq__(self, other): return True class MismatchedHash(FileHash): def __init__(self): return super().__init__('', '') def __eq__(self, other): return False class TestLoader(unittest.TestCase): def setUp(self): profile_data = { 'target': 'test', 'quoting': {}, 'outputs': { 'test': { 'type': 'redshift', 'host': 'localhost', 'schema': 'analytics', 'user': 'test', 'pass': 'test', 'dbname': 'test', 'port': 1, } } } root_project = { 'name': 'root', 'version': '0.1', 'profile': 'test', 'project-root': normalize('/usr/src/app'), } self.root_project_config = config_from_parts_or_dicts( project=root_project, profile=profile_data, cli_vars='{"test_schema_name": "foo"}' ) self.parser = mock.MagicMock() self.patched_result_builder = mock.patch('dbt.parser.manifest.make_parse_result') self.mock_result_builder = self.patched_result_builder.start() self.patched_result_builder.return_value = self._new_results() self.loader = manifest.ManifestLoader( self.root_project_config, {'root': self.root_project_config} ) def _new_results(self): return ParseResult(MatchingHash(), MatchingHash(), {}) def _mismatched_file(self, searched, name): return self._new_file(searched, name, False) def _matching_file(self, searched, name): return self._new_file(searched, name, True) def _new_file(self, searched, name, match): if match: checksum = MatchingHash() else: checksum = MismatchedHash() path = FilePath( searched_path=normalize(searched), relative_path=normalize(name), project_root=normalize(self.root_project_config.project_root), ) return SourceFile(path=path, checksum=checksum) def test_model_no_cache(self): source_file = self._matching_file('models', 'model_1.sql') self.parser.load_file.return_value = source_file old_results = None self.loader.parse_with_cache(source_file.path, self.parser, old_results) # there was nothing in the cache, so parse_file should get called # with a FileBlock that has the given source file in it self.parser.parse_file.assert_called_once_with(FileBlock(file=source_file)) def test_model_cache_hit(self): source_file = self._matching_file('models', 'model_1.sql') self.parser.load_file.return_value = source_file source_file_dupe = self._matching_file('models', 'model_1.sql') source_file_dupe.nodes.append('model.root.model_1') old_results = self._new_results() old_results.files[source_file_dupe.path.search_key] = source_file_dupe old_results.nodes = {'model.root.model_1': mock.MagicMock()} self.loader.parse_with_cache(source_file.path, self.parser, old_results) # there was a cache hit, so parse_file should never have been called self.parser.parse_file.assert_not_called() def test_model_cache_mismatch_checksum(self): source_file = self._mismatched_file('models', 'model_1.sql') self.parser.load_file.return_value = source_file source_file_dupe = self._mismatched_file('models', 'model_1.sql') source_file_dupe.nodes.append('model.root.model_1') old_results = self._new_results() old_results.files[source_file_dupe.path.search_key] = source_file_dupe old_results.nodes = {'model.root.model_1': mock.MagicMock()} self.loader.parse_with_cache(source_file.path, self.parser, old_results) # there was a cache checksum mismatch, so parse_file should get called # with a FileBlock that has the given source file in it self.parser.parse_file.assert_called_once_with(FileBlock(file=source_file)) def test_model_cache_missing_file(self): source_file = self._matching_file('models', 'model_1.sql') self.parser.load_file.return_value = source_file source_file_different = self._matching_file('models', 'model_2.sql') source_file_different.nodes.append('model.root.model_2') old_results = self._new_results() old_results.files[source_file_different.path.search_key] = source_file_different old_results.nodes = {'model.root.model_2': mock.MagicMock()} self.loader.parse_with_cache(source_file.path, self.parser, old_results) # the filename wasn't in the cache, so parse_file should get called # with a FileBlock that has the given source file in it. self.parser.parse_file.assert_called_once_with(FileBlock(file=source_file))