def find_includes(filepath, filelist=set()): """ Recursively searches a .cpp file for #include directives and returns a set of all of them. :return: a list of all includes found """ filedir = os.path.dirname(filepath) try: log.debug_indent() for include_line in file.grep(r'^\s*#\s*include\s+("(.*)"|<(.*)>)\s*$', filepath): m = include_line['match'] index = include_line['index'] include = find_include( m.group(2), filedir) or find_include_in_dirs( m.group(2)) or find_include_in_dirs(m.group(3)) if include: if include in filelist: log.d(m.group(0), '->', include, '(already processed)') else: log.d(m.group(0), '->', include) filelist.add(include) filelist = find_includes(include, filelist) else: log.d('not found:', m.group(0)) finally: log.debug_unindent() return filelist
def get_tests(): global regex, target, pyrs, current_dir, linux if regex: pattern = re.compile(regex) if target: # In Linux, the build targets are located elsewhere than on Windows # Go over all the tests from a "manifest" we take from the result of the last CMake # run (rather than, for example, looking for test-* in the build-directory): if linux: manifestfile = target + '/CMakeFiles/TargetDirectories.txt' else: manifestfile = target + '/../CMakeFiles/TargetDirectories.txt' # log.d( manifestfile ) for manifest_ctx in file.grep( r'(?<=unit-tests/build/)\S+(?=/CMakeFiles/test-\S+.dir$)', manifestfile): # We need to first create the test name so we can see if it fits the regex testdir = manifest_ctx['match'].group(0) # "log/internal/test-all" # log.d( testdir ) testparent = os.path.dirname(testdir) # "log/internal" if testparent: testname = 'test-' + testparent.replace( '/', '-') + '-' + os.path.basename(testdir)[ 5:] # "test-log-internal-all" else: testname = testdir # no parent folder so we get "test-all" if regex and not pattern.search(testname): continue if linux: exe = target + '/unit-tests/build/' + testdir + '/' + testname else: exe = target + '/' + testname + '.exe' yield libci.ExeTest(testname, exe, context) # Python unit-test scripts are in the same directory as us... we want to consider running them # (we may not if they're live and we have no pyrealsense2.pyd): for py_test in file.find(current_dir, '(^|/)test-.*\.py'): testparent = os.path.dirname( py_test) # "log/internal" <- "log/internal/test-all.py" if testparent: testname = 'test-' + testparent.replace( '/', '-') + '-' + os.path.basename(py_test)[5:-3] # remove .py else: testname = os.path.basename(py_test)[:-3] if regex and not pattern.search(testname): continue yield libci.PyTest(testname, py_test, context)
def check_log_for_fails(path_to_log, testname, configuration=None, repetition=1): # Normal logs are expected to have in last line: # "All tests passed (11 assertions in 1 test case)" # Tests that have failures, however, will show: # "test cases: 1 | 1 failed # assertions: 9 | 6 passed | 3 failed" # We make sure we look at the log written by the last run of the test by ignoring anything before the last # line with "----...---" that separate between 2 separate runs of he test if path_to_log is None: return False results = None for ctx in file.grep( r'^test cases:\s*(\d+) \|\s*(\d+) (passed|failed)|^----------TEST-SEPARATOR----------$', path_to_log): m = ctx['match'] if m.string == "----------TEST-SEPARATOR----------": results = None else: results = m if not results: return False total = int(results.group(1)) passed = int(results.group(2)) if results.group(3) == 'failed': # "test cases: 1 | 1 failed" passed = total - passed if passed < total: if total == 1 or passed == 0: desc = 'failed' else: desc = str(total - passed) + ' of ' + str(total) + ' failed' if log.is_verbose_on(): log.e(log.red + testname + log.reset + ': ' + configuration_str(configuration, repetition, suffix=' ') + desc) log.i('Log: >>>') log.out() file.cat(path_to_log) log.out('<<<') else: log.e(log.red + testname + log.reset + ': ' + configuration_str(configuration, repetition, suffix=' ') + desc + '; see ' + path_to_log) return True return False
def find_includes(filepath): filelist = set() filedir = os.path.dirname(filepath) for context in file.grep('^\s*#\s*include\s+"(.*)"\s*$', filepath): m = context['match'] index = context['index'] include = m.group(1) if not os.path.isabs(include): include = os.path.normpath(filedir + '/' + include) include = include.replace('\\', '/') if os.path.exists(include): filelist.add(include) filelist |= find_includes(include) return filelist
def __init__( self, source, line_prefix ): """ :param source: The path to the text file :param line_prefix: A regex to denote a directive (must be first thing in a line), which will be immediately followed by the directive itself and optional arguments """ TestConfig.__init__(self) # Parse the python regex = r'^' + line_prefix + r'(\S+)((?:\s+\S+)*?)\s*(?:#\s*(.*))?$' for context in file.grep( regex, source ): match = context['match'] directive = match.group(1) text_params = match.group(2).strip() params = [s for s in text_params.split()] comment = match.group(3) if directive == 'device': #log.d( ' configuration:', params ) if not params: log.e( source + '+' + str(context['index']) + ': device directive with no devices listed' ) elif 'each' in text_params.lower() and len(params) > 1: log.e( source + '+' + str(context['index']) + ': each() cannot be used in combination with other specs', params ) elif 'each' in text_params.lower() and not re.fullmatch( r'each\(.+\)', text_params, re.IGNORECASE ): log.e( source + '+' + str(context['index']) + ': invalid \'each\' syntax:', params ) else: self._configurations.append( params ) elif directive == 'priority': if len(params) == 1 and params[0].isdigit(): self._priority = int( params[0] ) else: log.e( source + '+' + str(context['index']) + ': priority directive with invalid parameters:', params ) elif directive == 'timeout': if len(params) == 1 and params[0].isdigit(): self._timeout = int( params[0] ) else: log.e( source + '+' + str(context['index']) + ': timeout directive with invalid parameters:', params ) elif directive == 'tag': self._tags.update(params) elif directive == 'flag': self._flags.update( params ) else: log.e( source + '+' + str(context['index']) + ': invalid directive "' + directive + '"; ignoring' )
def process_cpp(dir, builddir): global regex, required_tags, list_only, available_tags, tests_and_tags found = [] shareds = [] statics = [] if regex: pattern = re.compile(regex) for f in file.find(dir, '(^|/)test-.*\.cpp$'): testdir = os.path.splitext(f)[ 0] # "log/internal/test-all" <- "log/internal/test-all.cpp" testparent = os.path.dirname(testdir) # "log/internal" # We need the project name unique: keep the path but make it nicer: testname = 'test-' + testparent.replace( '/', '-') + '-' + os.path.basename(testdir)[ 5:] # "test-log-internal-all" if regex and not pattern.search(testname): continue if required_tags or list_tags: config = libci.TestConfigFromCpp(dir + os.sep + f) if not all(tag in config.tags for tag in required_tags): continue available_tags.update(config.tags) if list_tests: tests_and_tags[testname] = config.tags if testname not in tests_and_tags: tests_and_tags[testname] = None if list_only: continue # Each CMakeLists.txt sits in its own directory os.makedirs(builddir + '/' + testdir, exist_ok=True) # "build/log/internal/test-all" # Build the list of files we want in the project: # At a minimum, we have the original file, plus any common files filelist = [dir + '/' + f, '${ELPP_FILES}', '${CATCH_FILES}'] # Add any "" includes specified in the .cpp that we can find includes = find_includes(dir + '/' + f) # Add any files explicitly listed in the .cpp itself, like this: # //#cmake:add-file <filename> # Any files listed are relative to $dir shared = False static = False for context in file.grep('^//#cmake:\s*', dir + '/' + f): m = context['match'] index = context['index'] cmd, *rest = context['line'][m.end():].split() if cmd == 'add-file': for additional_file in rest: files = additional_file if not os.path.isabs(additional_file): files = dir + '/' + testparent + '/' + additional_file files = glob(files) if not files: log.e(f + '+' + str(index) + ': no files match "' + additional_file + '"') for abs_file in files: abs_file = os.path.normpath(abs_file) abs_file = abs_file.replace('\\', '/') if not os.path.exists(abs_file): log.e(f + '+' + str(index) + ': file not found "' + additional_file + '"') log.d(' add file:', abs_file) filelist.append(abs_file) if (os.path.splitext(abs_file)[0] == 'cpp'): # Add any "" includes specified in the .cpp that we can find includes |= find_includes(abs_file) elif cmd == 'static!': if len(rest): log.e(f + '+' + str(index) + ': unexpected arguments past \'' + cmd + '\'') elif shared: log.e(f + '+' + str(index) + ': \'' + cmd + '\' mutually exclusive with \'shared!\'') else: static = True elif cmd == 'shared!': if len(rest): log.e(f + '+' + str(index) + ': unexpected arguments past \'' + cmd + '\'') elif static: log.e(f + '+' + str(index) + ': \'' + cmd + '\' mutually exclusive with \'static!\'') else: shared = True else: log.e( f + '+' + str(index) + ': unknown cmd \'' + cmd + '\' (should be \'add-file\', \'static!\', or \'shared!\')') for include in includes: filelist.append(include) generate_cmake(builddir, testdir, testname, filelist) if static: statics.append(testdir) elif shared: shareds.append(testdir) else: found.append(testdir) return found, shareds, statics
def derive_config_from_text(self, source, line_prefix): # Configuration is made up of directives: # #test:<directive>[:[!]<context>] <param>* # If a context is not specified, the directive always applies. Any directive with a context # will only get applied if we're running under the context it specifies (! means not, so # !nightly means when not under nightly). regex = r'^' + line_prefix regex += r'([^\s:]+)' # 1: directive regex += r'(?::(\S+))?' # 2: optional context regex += r'((?:\s+\S+)*?)' # 3: params regex += r'\s*(?:#\s*(.*))?$' # 4: optional comment for line in file.grep(regex, source): match = line['match'] directive = match.group(1) directive_context = match.group(2) text_params = match.group(3).strip() params = [s for s in text_params.split()] comment = match.group(4) if directive_context: not_context = directive_context.startswith('!') if not_context: directive_context = directive_context[1:] # not_context | directive_ctx==context | RESULT # ----------- | ---------------------- | ------ # 0 | 0 | IGNORE # 0 | 1 | USE # 1 | 0 | USE # 1 | 1 | IGNORE if not_context == (directive_context == self.context): # log.d( "directive", line['line'], "ignored because of context mismatch with running context", # self.context) continue if directive == 'device': # log.d( ' configuration:', params ) if not params: log.e(source + '+' + str(line['index']) + ': device directive with no devices listed') elif 'each' in text_params.lower() and len(params) > 1: log.e( source + '+' + str(line['index']) + ': each() cannot be used in combination with other specs', params) elif 'each' in text_params.lower() and not re.fullmatch( r'each\(.+\)', text_params, re.IGNORECASE): log.e( source + '+' + str(line['index']) + ': invalid \'each\' syntax:', params) else: self._configurations.append(params) elif directive == 'priority': if len(params) == 1 and params[0].isdigit(): self._priority = int(params[0]) else: log.e( source + '+' + str(line['index']) + ': priority directive with invalid parameters:', params) elif directive == 'timeout': if len(params) == 1 and params[0].isdigit(): self._timeout = int(params[0]) else: log.e( source + '+' + str(line['index']) + ': timeout directive with invalid parameters:', params) elif directive == 'tag': self._tags.update(map(str.lower, params)) # tags are case-insensitive elif directive == 'flag': self._flags.update(params) elif directive == 'donotrun': if params: log.e( source + '+' + str(line['index']) + ': donotrun directive should not have parameters:', params) self._donotrun = True else: log.e(source + '+' + str(line['index']) + ': invalid directive "' + directive + '"; ignoring')