def reset(self, identifier, coderoot): """Resets the writer to work with a new executable.""" #A method writer has all the guts needed to generate the executable #We are just wrapping it and making sure the variable values get #recorded to temporary files for comparison etc. self.writer = MethodWriter(identifier, self.parser, self.testgen) self.identifier = identifier if self.libraryroot is not None: self.folders[identifier] = path.abspath(self.libraryroot) elif self.writer.group is not None and self.writer.group.staging is not None: #Determine the absolute path using the relative path specified in the group #staging directory attribute. from fortpy.tramp import coderelpath self.folders[identifier] = coderelpath(coderoot, self.writer.group.staging) else: raise ValueError( "Can't determine the staging directory for running the tests.") self._needs = None #The calling methods need to know whether the context (i.e. the staging folder) #of this executable writer has changed. This variable tracks that. newstage = self.folder != self.folders[identifier] self.folder = path.join(self.folders[identifier], identifier) #Create the directory for the executable files to be copied and written to. if not path.exists(self.folder): msg.okay("EXEC DIR: create {}".format(self.folder)) mkdir(self.folder) return newstage
def reset(self, identifier, coderoot): """Resets the writer to work with a new executable.""" #A method writer has all the guts needed to generate the executable #We are just wrapping it and making sure the variable values get #recorded to temporary files for comparison etc. self.writer = MethodWriter(identifier, self.parser, self.testgen) self.identifier = identifier if self.libraryroot is not None: self.folders[identifier] = path.abspath(self.libraryroot) elif self.writer.group is not None and self.writer.group.staging is not None: #Determine the absolute path using the relative path specified in the group #staging directory attribute. from fortpy.tramp import coderelpath self.folders[identifier] = coderelpath(coderoot, self.writer.group.staging) else: raise ValueError("Can't determine the staging directory for running the tests.") self._needs = None #The calling methods need to know whether the context (i.e. the staging folder) #of this executable writer has changed. This variable tracks that. newstage = self.folder != self.folders[identifier] self.folder = path.join(self.folders[identifier], identifier) #Create the directory for the executable files to be copied and written to. if not path.exists(self.folder): msg.okay("EXEC DIR: create {}".format(self.folder)) mkdir(self.folder) return newstage
class ExecutableGenerator(object): """Generates a fortran executable to perform unit tests for a given subroutine or function. :arg parser: an instance of the code parser for inter-module access :arg folder: the folder in which to generate the code files and execs. :arg testgen: the TestGenerator instance that owns this executable generator. """ def __init__(self, parser, folder, testgen): #We need the code parser because the pre-reqs specified may be in other #modules and may have pre-reqs of their own. This way, we can find #them all easily. self.parser = parser self.libraryroot = folder """Specifies a staging directory to override the one specified in the <group> tag (if one was specified there).""" self.testgen = testgen self.folders = {} """Dictionary of full paths to the staging directory for each unit test being generated by the framework. """ self.folder = None """The full path to the *current* staging directory for the executable whose tests are being generated. """ self._needs = None def needs(self): """Returns a list of all the modules that this executable needs to run correctly.""" if self._needs is None: self._needs = self._calc_needs() return self._needs def _calc_needs(self): """Calculates a list of all the modules that this executable needs to run correctly.""" result = [] for modk in self.writer.uses(): if modk not in result: result.append(modk) #We also need to look up the dependencies of each of these modules recursed = list(result) for i in range(len(result)): module = result[i] self._process_module_needs(module, i, recursed) return recursed def _process_module_needs(self, module, i, result): """Adds the module and its dependencies to the result list.""" #Some code might decide to use the fortpy module methods for general #development, ignore it since we know it will be present in the end. if module == "fortpy": return #See if the parser has alread loaded this module. if module not in self.parser.modules: self.parser.load_dependency(module, True, True, False) #It is possible that the parser couldn't find it, if so #we can't create the executable! if module in self.parser.modules: modneeds = self.parser.modules[module].needs for modn in modneeds: if modn not in result: #Since this module depends on the other, insert the other #above it in the list. result.insert(i, modn) else: x = result.index(modn) if x > i: #We need to move this module higher up in the food chain #because it is needed sooner. result.remove(modn) result.insert(i, modn) newi = result.index(modn) self._process_module_needs(modn, newi, result) else: raise ValueError("unable to find module {}.".format(module)) def reset(self, identifier, coderoot): """Resets the writer to work with a new executable.""" #A method writer has all the guts needed to generate the executable #We are just wrapping it and making sure the variable values get #recorded to temporary files for comparison etc. self.writer = MethodWriter(identifier, self.parser, self.testgen) self.identifier = identifier if self.libraryroot is not None: self.folders[identifier] = path.abspath(self.libraryroot) elif self.writer.group is not None and self.writer.group.staging is not None: #Determine the absolute path using the relative path specified in the group #staging directory attribute. from fortpy.tramp import coderelpath self.folders[identifier] = coderelpath(coderoot, self.writer.group.staging) else: raise ValueError( "Can't determine the staging directory for running the tests.") self._needs = None #The calling methods need to know whether the context (i.e. the staging folder) #of this executable writer has changed. This variable tracks that. newstage = self.folder != self.folders[identifier] self.folder = path.join(self.folders[identifier], identifier) #Create the directory for the executable files to be copied and written to. if not path.exists(self.folder): msg.okay("EXEC DIR: create {}".format(self.folder)) mkdir(self.folder) return newstage def write(self, testid, coderoot): """Writes the fortran program file for the executable specified. :arg testid: the identifier of the test to construct the executable for. """ lines = [] identifier = self.writer.tests[testid].identifier #First off, we need to start the program and set the module dependencies. lines.append("!!<summary>Auto-generated unit test for {}\n".format( self.identifier)) lines.append("!!using FORTPY. Generated on {}.\n".format( datetime.now())) lines.append("!!{}</summary>\n".format( self.writer.tests[testid].description)) lines.append("PROGRAM UNITTEST_{}\n".format( self.writer.finders[testid].executable.name)) lines.append(self._get_uses(testid)) lines.append(" implicit none\n") #Next add the variable declarations and initializations and the calls #to execute the pre-req methods and the one we are trying to test. lines.append(self.writer.lines(testid, coderoot)) lines.append("\nEND PROGRAM UNITTEST_{}".format( self.writer.finders[testid].executable.name)) with open(path.join(self.folder, "{}.f90".format(identifier)), 'w') as f: f.writelines(lines) def makefile(self, identifier): """Generates a makefile to create the unit testing executable for the specified test identifier. :arg identifier: the id of the test that this executable should be made for. """ allneeds = self.needs() #We need to see whether to include the pre-compiler directive or not. precompile = False for needed in allneeds: if self.parser.modules[needed].precompile: precompile = True break lines = [] makepath = path.join(self.folder, "Makefile.{}".format(identifier)) makefile(identifier, allneeds, makepath, self.identifier, precompile, parser=self.parser) def _get_uses(self, testid): """Gets a list of use statements to add to the program code.""" #The writer can extract a dictionary of module dependencies for us. alluses = self.writer.uses() #Now we just need to generate the code statements. uselist = [] for module in alluses: if module == self.writer.finders[testid].module.name: uselist.append("use {}".format(module)) else: uselist.append("use {}, only: {}".format( module, ", ".join(alluses[module]))) #Last of all, we need to append the module that handles interaction with #the fortran results and the python testing framework. if "fortpy" not in alluses: uselist.append("use fortpy\n") return self._tabjoin(uselist) def _tabjoin(self, values): """Joins a list of values with \t\n.""" return " {}".format("\n ".join(values))
class ExecutableGenerator(object): """Generates a fortran executable to perform unit tests for a given subroutine or function. :arg parser: an instance of the code parser for inter-module access :arg folder: the folder in which to generate the code files and execs. :arg testgen: the TestGenerator instance that owns this executable generator. """ def __init__(self, parser, folder, testgen): #We need the code parser because the pre-reqs specified may be in other #modules and may have pre-reqs of their own. This way, we can find #them all easily. self.parser = parser self.libraryroot = folder """Specifies a staging directory to override the one specified in the <group> tag (if one was specified there).""" self.testgen = testgen self.folders = {} """Dictionary of full paths to the staging directory for each unit test being generated by the framework. """ self.folder = None """The full path to the *current* staging directory for the executable whose tests are being generated. """ self._needs = None def needs(self): """Returns a list of all the modules that this executable needs to run correctly.""" if self._needs is None: self._needs = self._calc_needs() return self._needs def _calc_needs(self): """Calculates a list of all the modules that this executable needs to run correctly.""" result = [] for modk in self.writer.uses(): if modk not in result: result.append(modk) #We also need to look up the dependencies of each of these modules recursed = list(result) for i in range(len(result)): module = result[i] self._process_module_needs(module, i, recursed) return recursed def _process_module_needs(self, module, i, result): """Adds the module and its dependencies to the result list.""" #Some code might decide to use the fortpy module methods for general #development, ignore it since we know it will be present in the end. if module == "fortpy": return #See if the parser has alread loaded this module. if module not in self.parser.modules: self.parser.load_dependency(module, True, True, False) #It is possible that the parser couldn't find it, if so #we can't create the executable! if module in self.parser.modules: modneeds = self.parser.modules[module].needs for modn in modneeds: if modn not in result: #Since this module depends on the other, insert the other #above it in the list. result.insert(i, modn) else: x = result.index(modn) if x > i: #We need to move this module higher up in the food chain #because it is needed sooner. result.remove(modn) result.insert(i, modn) newi = result.index(modn) self._process_module_needs(modn, newi, result) else: raise ValueError("unable to find module {}.".format(module)) def reset(self, identifier, coderoot): """Resets the writer to work with a new executable.""" #A method writer has all the guts needed to generate the executable #We are just wrapping it and making sure the variable values get #recorded to temporary files for comparison etc. self.writer = MethodWriter(identifier, self.parser, self.testgen) self.identifier = identifier if self.libraryroot is not None: self.folders[identifier] = path.abspath(self.libraryroot) elif self.writer.group is not None and self.writer.group.staging is not None: #Determine the absolute path using the relative path specified in the group #staging directory attribute. from fortpy.tramp import coderelpath self.folders[identifier] = coderelpath(coderoot, self.writer.group.staging) else: raise ValueError("Can't determine the staging directory for running the tests.") self._needs = None #The calling methods need to know whether the context (i.e. the staging folder) #of this executable writer has changed. This variable tracks that. newstage = self.folder != self.folders[identifier] self.folder = path.join(self.folders[identifier], identifier) #Create the directory for the executable files to be copied and written to. if not path.exists(self.folder): msg.okay("EXEC DIR: create {}".format(self.folder)) mkdir(self.folder) return newstage def write(self, testid, coderoot): """Writes the fortran program file for the executable specified. :arg testid: the identifier of the test to construct the executable for. """ lines = [] identifier = self.writer.tests[testid].identifier #First off, we need to start the program and set the module dependencies. lines.append("!!<summary>Auto-generated unit test for {}\n".format( self.identifier)) lines.append("!!using FORTPY. Generated on {}.\n".format(datetime.now())) lines.append("!!{}</summary>\n".format(self.writer.tests[testid].description)) lines.append("PROGRAM UNITTEST_{}\n".format(self.writer.finders[testid].executable.name)) lines.append(self._get_uses(testid)) lines.append(" implicit none\n") #Next add the variable declarations and initializations and the calls #to execute the pre-req methods and the one we are trying to test. lines.append(self.writer.lines(testid, coderoot)) lines.append("\nEND PROGRAM UNITTEST_{}".format(self.writer.finders[testid].executable.name)) with open(path.join(self.folder, "{}.f90".format(identifier)), 'w') as f: f.writelines(lines) def makefile(self, identifier): """Generates a makefile to create the unit testing executable for the specified test identifier. :arg identifier: the id of the test that this executable should be made for. """ allneeds = self.needs() #We need to see whether to include the pre-compiler directive or not. precompile = False for needed in allneeds: if self.parser.modules[needed].precompile: precompile = True break lines = [] makepath = path.join(self.folder, "Makefile.{}".format(identifier)) makefile(identifier, allneeds, makepath, self.identifier, precompile, parser=self.parser) def _get_uses(self, testid): """Gets a list of use statements to add to the program code.""" #The writer can extract a dictionary of module dependencies for us. alluses = self.writer.uses() #Now we just need to generate the code statements. uselist = [] for module in alluses: if module == self.writer.finders[testid].module.name: uselist.append("use {}".format(module)) else: uselist.append("use {}, only: {}".format(module, ", ".join(alluses[module]))) #Last of all, we need to append the module that handles interaction with #the fortran results and the python testing framework. if "fortpy" not in alluses: uselist.append("use fortpy\n") return self._tabjoin(uselist) def _tabjoin(self, values): """Joins a list of values with \t\n.""" return " {}".format("\n ".join(values))
class ExecutableGenerator(object): """Generates a fortran executable to perform unit tests for a given subroutine or function. :arg parser: an instance of the code parser for inter-module access :arg folder: the folder in which to generate the code files and execs. :arg testgen: the TestGenerator instance that owns this executable generator. """ def __init__(self, parser, folder, testgen): #We need the code parser because the pre-reqs specified may be in other #modules and may have pre-reqs of their own. This way, we can find #them all easily. self.parser = parser self.libraryroot = folder """Specifies a staging directory to override the one specified in the <group> tag (if one was specified there).""" self.testgen = testgen self.folders = {} """Dictionary of full paths to the staging directory for each unit test being generated by the framework. """ self.folder = None """The full path to the *current* staging directory for the executable whose tests are being generated. """ self._needs = None def needs(self): """Returns a list of all the modules that this executable needs to run correctly.""" if self._needs is None: from fortpy.code import order_module_dependencies self._needs = order_module_dependencies(self.writer.uses(), self.parser) return self._needs def reset(self, identifier, coderoot): """Resets the writer to work with a new executable.""" #A method writer has all the guts needed to generate the executable #We are just wrapping it and making sure the variable values get #recorded to temporary files for comparison etc. self.writer = MethodWriter(identifier, self.parser, self.testgen, stagedir=self.libraryroot) self.identifier = identifier if self.libraryroot is not None: self.folders[identifier] = path.abspath(self.libraryroot) elif self.writer.group is not None and self.writer.group.staging is not None: #Determine the absolute path using the relative path specified in the group #staging directory attribute. from fortpy.tramp import coderelpath self.folders[identifier] = coderelpath(coderoot, self.writer.group.staging) else: raise ValueError("Can't determine the staging directory for running the tests.") self._needs = None #The calling methods need to know whether the context (i.e. the staging folder) #of this executable writer has changed. This variable tracks that. newstage = self.folder != self.folders[identifier] self.folder = path.join(self.folders[identifier], identifier) #Create the directory for the executable files to be copied and written to. if not path.exists(self.folder): msg.okay("EXEC DIR: create {}".format(self.folder)) mkdir(self.folder) return newstage def write(self, testid, coderoot): """Writes the fortran program file for the executable specified. :arg testid: the identifier of the test to construct the executable for. """ lines = [] identifier = self.writer.tests[testid].identifier #First off, we need to start the program and set the module dependencies. lines.append("!!<summary>Auto-generated unit test for {}\n".format( self.identifier)) lines.append("!!using FORTPY. Generated on {}.\n".format(datetime.now())) lines.append("!!{}</summary>\n".format(self.writer.tests[testid].description)) lines.append("PROGRAM UNITTEST_{}\n".format(self.writer.finders[testid].executable.name)) lines.append(self._get_uses(testid)) lines.append(" implicit none\n") #Next add the variable declarations and initializations and the calls #to execute the pre-req methods and the one we are trying to test. lines.append(self.writer.lines(testid, coderoot)) lines.append("\nEND PROGRAM UNITTEST_{}".format(self.writer.finders[testid].executable.name)) with open(path.join(self.folder, "{}.f90".format(identifier)), 'w') as f: f.writelines(lines) def makefile(self, identifier): """Generates a makefile to create the unit testing executable for the specified test identifier. :arg identifier: the id of the test that this executable should be made for. """ allneeds = self.needs() #We need to see whether to include the pre-compiler directive or not. precompile = False for needed in allneeds: if needed=="fortpy" or needed=="fpy_auxiliary": continue if self.parser.modules[needed].precompile: precompile = True break lines = [] #Determine whether the compilation should produce headers on the output screen. from fortpy.msg import will_print verbose = will_print(2) makepath = path.join(self.folder, "Makefile.{}".format(identifier)) makefile(identifier, allneeds, makepath, self.identifier, precompile, parser=self.parser, inclfpyaux=self.writer.autoclass, verbose=verbose) def _get_uses(self, testid): """Gets a list of use statements to add to the program code.""" #The writer can extract a dictionary of module dependencies for us. alluses = self.writer.uses() #Now we just need to generate the code statements. uselist = [] for module in alluses: if module == self.writer.finders[testid].module.name: uselist.append("use {}".format(module)) elif module == "fpy_auxiliary": uselist.append("use fpy_auxiliary") else: uselist.append("use {}, only: {}".format(module, ", ".join(alluses[module]))) #Last of all, we need to append the module that handles interaction with #the fortran results and the python testing framework. if "fortpy" not in alluses: uselist.append("use fortpy\n") return self._tabjoin(uselist) def _tabjoin(self, values): """Joins a list of values with \t\n.""" return " {}".format("\n ".join(values))