1# Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com 2# 3# Part of "Nuitka", an optimizing Python compiler that is compatible and 4# integrates with CPython, but also works on its own. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18""" Search modes for Nuitka's test runner. 19 20The test runner can handle found errors, skip tests, etc. with search 21modes, which are implemented here. 22""" 23 24import hashlib 25import os 26import sys 27 28from nuitka.utils.FileOperations import areSamePaths 29 30 31class SearchModeBase(object): 32 def __init__(self): 33 self.may_fail = [] 34 35 def consider(self, dirname, filename): 36 # Virtual method, pylint: disable=no-self-use,unused-argument 37 return True 38 39 def finish(self): 40 pass 41 42 def abortOnFinding(self, dirname, filename): 43 for candidate in self.may_fail: 44 if self._match(dirname, filename, candidate): 45 return False 46 47 return True 48 49 def getExtraFlags(self, dirname, filename): 50 # Virtual method, pylint: disable=no-self-use,unused-argument 51 return [] 52 53 def mayFailFor(self, *names): 54 self.may_fail += names 55 56 @classmethod 57 def _match(cls, dirname, filename, candidate): 58 parts = [dirname, filename] 59 60 while None in parts: 61 parts.remove(None) 62 assert parts 63 64 path = os.path.join(*parts) 65 66 candidates = ( 67 dirname, 68 filename, 69 filename.replace(".py", ""), 70 filename.split(".")[0], 71 path, 72 path.replace(".py", ""), 73 ) 74 75 candidate2 = os.path.relpath( 76 candidate, os.path.dirname(sys.modules["__main__"].__file__) 77 ) 78 79 return candidate.rstrip("/") in candidates or candidate2 in candidates 80 81 def exit(self, message): 82 # Virtual method, pylint: disable=no-self-use 83 sys.exit(message) 84 85 def isCoverage(self): 86 # Virtual method, pylint: disable=no-self-use 87 return False 88 89 def onErrorDetected(self, message): 90 self.exit(message) 91 92 93class SearchModeImmediate(SearchModeBase): 94 pass 95 96 97class SearchModeByPattern(SearchModeBase): 98 def __init__(self, start_at): 99 SearchModeBase.__init__(self) 100 101 self.active = False 102 self.start_at = start_at 103 104 def consider(self, dirname, filename): 105 if self.active: 106 return True 107 108 self.active = self._match(dirname, filename, self.start_at) 109 return self.active 110 111 def finish(self): 112 if not self.active: 113 sys.exit("Error, became never active.") 114 115 116class SearchModeResume(SearchModeBase): 117 def __init__(self, tests_path): 118 SearchModeBase.__init__(self) 119 120 tests_path = os.path.normcase(os.path.abspath(tests_path)) 121 version = sys.version 122 123 if str is not bytes: 124 tests_path = tests_path.encode("utf8") 125 version = version.encode("utf8") 126 127 case_hash = hashlib.md5(tests_path) 128 case_hash.update(version) 129 130 from .Common import getTestingCacheDir 131 132 cache_filename = os.path.join(getTestingCacheDir(), case_hash.hexdigest()) 133 134 self.cache_filename = cache_filename 135 136 if os.path.exists(cache_filename): 137 with open(cache_filename, "r") as f: 138 self.resume_from = f.read() or None 139 else: 140 self.resume_from = None 141 142 self.active = not self.resume_from 143 144 def consider(self, dirname, filename): 145 parts = [dirname, filename] 146 147 while None in parts: 148 parts.remove(None) 149 assert parts 150 151 path = os.path.join(*parts) 152 153 if self.active: 154 with open(self.cache_filename, "w") as f: 155 f.write(path) 156 157 return True 158 159 if areSamePaths(path, self.resume_from): 160 self.active = True 161 162 return self.active 163 164 def finish(self): 165 os.unlink(self.cache_filename) 166 if not self.active: 167 sys.exit("Error, became never active, restarting next time.") 168 169 170class SearchModeCoverage(SearchModeBase): 171 def getExtraFlags(self, dirname, filename): 172 return ["coverage"] 173 174 def isCoverage(self): 175 return True 176 177 178class SearchModeOnly(SearchModeByPattern): 179 def __init__(self, start_at): 180 SearchModeByPattern.__init__(self, start_at=start_at) 181 182 self.done = False 183 184 def consider(self, dirname, filename): 185 if self.done: 186 return False 187 else: 188 active = SearchModeByPattern.consider( 189 self, dirname=dirname, filename=filename 190 ) 191 192 if active: 193 self.done = True 194 195 return active 196 197 198class SearchModeAll(SearchModeBase): 199 def __init__(self): 200 SearchModeBase.__init__(self) 201 self.total_errors = 0 202 203 def updateTotalErrors(self): 204 self.total_errors += 1 205 206 def onErrorDetected(self, message): 207 self.updateTotalErrors() 208 209 def finish(self): 210 self.exit("Total " + str(self.total_errors) + " error(s) found.") 211