def get_function(self, funcname): """Given a function name, return it. Exit on error if not found. """ # Import the function sys.path.insert(0, os.path.dirname(self.filename)) module = ".".join(funcname.split(".")[:-1]) funcname = funcname.split(".")[-1] try: module = import_module(module) func = getattr(module, funcname) if func is None: bot.exit(f"Cannot find {funcname}.") except: bot.exit(f"Cannot import grid function {funcname}") return func
def test_basic( funcname, module, filename, func=None, args=None, returns=None, interactive=False, metrics=None, ): """test basic is a worker version of the task.test_basic function. If a function is not provided, funcname, module, and filename are required to retrieve it. A function can only be provided directly if it is pickle serializable (multiprocessing would require this). It works equivalently but is not attached to a class, and returns a list of values for [passed, result, out, err, raises] Arguments: - funcname (str) : the name of the function to import - module (str) : the base module to get the function from - func (Function) : if running serial, function can be directly provided - args (dict) : dictionary of arguments - returns (type) : a returns type to test for - interactive (bool) : run in interactive mode (giving user shell) - metrics (list) : one or more metrics (decorators) to run. """ metrics = metrics or [] if not func: sys.path.insert(0, os.path.dirname(filename)) func = get_function( module=module, funcname=funcname, args=args, filename=filename ) # Figure out how to apply multiple originalfunc = func passed = False result = None raises = None out = [] err = [] # import the decorators here (currently only support decorators from gridtest for metric in metrics: if not metric.startswith("@"): continue metric = re.sub("^[@]", "", metric) try: gt = import_module("gridtest.decorators") decorator = getattr(gt, metric) # Update func to include wrapper func = decorator(func) # Fallback to support for custom modules except: try: metric_module = metric.split(".")[0] mm = import_module(metric_module) for piece in metric.split(".")[1:]: decorator = getattr(mm, piece) # Update func to include wrapper func = decorator(func) except: out.append(f"Warning, unable to import decorator @{metric}") # Interactive mode means giving the user console control if interactive: print_interactive(**locals()) try: import IPython IPython.embed() except: import code code.interact(local=locals()) if not func: err = [f"Cannot find function {funcname}"] else: # Subset arguments down to those allowed args = intersect_args(originalfunc, args) passed, error = test_types(originalfunc, args, returns) err += error # if type doesn't pass, TypeError, otherwise continue if not passed: raises = "TypeError" else: # Run and capture output and error try: with Capturing() as output: result = func(**args) if output: std = output.pop(0) out += std.get("out") err += std.get("err") passed = True except Exception as e: raises = type(e).__name__ message = str(e) if message: err.append(message) return [passed, result, out, err, raises]
def substitute_func(value, funcs=None): """Given a value, determine if it contains a function substitution, and if it's one an important function (e.g., one from gridtest.helpers) return the value with the function applied. Arguments: - value (str) : the value to do the substitution for. - funcs (dict) : lookup dictionary of functions to be used Notes: A function should be in the format: {% tempfile.mkdtemp %} (global import) or a function in gridtest.func in the format {% tmp_path %}. If arguments are supplied, they should be in the format {% tmp_path arg1=1 arg2=2 %} """ # Numbers cannot have replacement if not isinstance(value, str): return value # First do substitutions of variables for template in re.findall("{%.+%}", value): varname = re.sub("({%|%})", "", template) params = [x.strip() for x in varname.split(" ") if x] # Split module.name.func into module.name func modulename = params.pop(0).rsplit(".", 1)[0] funcpath = modulename[1:] func = None # Case 1: we have a known gridtest function if modulename in GRIDTEST_FUNCS: funcpath = modulename modulename = "gridtest.func" # Case 2: a function is supplied directly in the lookup elif funcs and modulename in funcs: func = funcs.get(modulename) # Case 3: Custom module provided by the user else: funcpath = funcpath[0] # The function path needs to be provided if not funcpath and not func: sys.exit(f"A function name must be provided for {varname}") # If used from within Python, the function might be supplied if not func: try: module = import_module(modulename) func = getattr(module, funcpath) except: sys.exit(f"Cannot import module {modulename}") # If function is found, get value if not func: sys.exit( f"Cannot import function {funcpath} from module {modulename}") kwargs = {} params = {x.split("=")[0]: x.split("=")[1] for x in params} # Clean up parameters based on intuited types for paramname, paramvalue in params.items(): if paramvalue == "None": paramvalue = None # Booleans elif paramvalue == "True": paramvalue = True elif paramvalue == "False": paramvalue = False # No quotes and all numeric, probably int elif re.search("^[0-9]+$", paramvalue): paramvalue = int(paramvalue) # One decimal, all numbers, probably float elif re.search("^[0-9]+[.]([0-9]+)?$", paramvalue): paramvalue = float(paramvalue) # Explicitly a string with quotes elif re.search('^(".+")$', paramvalue): paramvalue = paramvalue.strip('"') elif re.search("^('.+')$", paramvalue): paramvalue = paramvalue.strip("'") kwargs[paramname] = paramvalue new_value = func(**kwargs) value = re.sub(template, value, new_value) return value