diff --git a/ChangeLog.md b/ChangeLog.md index 84035c07..5db65559 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `--seed=N` option to specify random seed for reproducibility (0 uses time-based seed, implies --shuffle) - Fisher-Yates shuffle algorithm implemented in `TestSuite` module - Both unit tests and integration tests included +- Multi-line continuation support for pFUnit macros (issue #532) + - Assertion macros (e.g., `@assertEqual`) now support Fortran `&` line continuation + - Whitespace is preserved exactly as written when joining continued lines ## [4.16.0] - 2026-02-23 diff --git a/bin/funit/pFUnitParser.py b/bin/funit/pFUnitParser.py index 8bf459ef..a94b3cd6 100644 --- a/bin/funit/pFUnitParser.py +++ b/bin/funit/pFUnitParser.py @@ -6,44 +6,77 @@ import sysconfig import posixpath import re + # from parseBrackets import parseBrackets from .parseDirectiveArgs import parseDirectiveArguments + class MyError(Exception): def __init__(self, value): self.value = value + def __str__(self): return repr(self.value) -assert_operands = {'fail': 0, 'equal': 2, 'notequal': 2, 'true': 1, 'false': 1, - 'lessthan': 2, 'lessthanorequal': 2, 'greaterthan': 2, - 'greaterthanorequal': 2, 'notequal': 2, - 'relativelyequal': 2, 'isinfinite': 1, 'isfinite': 1, - 'isnan': 1, 'ismemberof': 2, 'contains': 2, 'any': 1, - 'all': 1, 'notall': 1, 'none': 1, 'ispermutationof': 2, - 'exceptionraised': 0, 'sameshape': 2, 'that': 2, '_that': 2} + +assert_operands = { + "fail": 0, + "equal": 2, + "notequal": 2, + "true": 1, + "false": 1, + "lessthan": 2, + "lessthanorequal": 2, + "greaterthan": 2, + "greaterthanorequal": 2, + "notequal": 2, + "relativelyequal": 2, + "isinfinite": 1, + "isfinite": 1, + "isnan": 1, + "ismemberof": 2, + "contains": 2, + "any": 1, + "all": 1, + "notall": 1, + "none": 1, + "ispermutationof": 2, + "exceptionraised": 0, + "sameshape": 2, + "that": 2, + "_that": 2, +} + def cppSetLineAndFile(line, file): - if sysconfig.get_platform() == 'mingw' or sysconfig.get_platform().startswith('win-'): - return "#line " + str(line) + ' "' + file.replace(sep,posixpath.sep) + '"\n' + if sysconfig.get_platform() == "mingw" or sysconfig.get_platform().startswith( + "win-" + ): + return "#line " + str(line) + ' "' + file.replace(sep, posixpath.sep) + '"\n' else: return "#line " + str(line) + ' "' + file + '"\n' + def getSubroutineName(line): try: - m = re.match(r'\s*subroutine\s+(\w*)\s*(\([\w\s,]*\))?\s*(!.*)*$', line, re.IGNORECASE) + m = re.match( + r"\s*subroutine\s+(\w*)\s*(\([\w\s,]*\))?\s*(!.*)*$", line, re.IGNORECASE + ) return m.groups()[0] except: - raise MyError('Improper format in declaration of test procedure.') + raise MyError("Improper format in declaration of test procedure.") + -def parseArgsFirstRest(directiveName,line): +def parseArgsFirstRest(directiveName, line): """If the @-directive has more than one argument, parse into first and rest strings. Added for assertAssociated. """ - argStr = ''; - if directiveName != '': - m = re.match(r'\s*'+directiveName+r'\s*\((.*\w.*)\)\s*$',line,re.IGNORECASE) + argStr = "" + if directiveName != "": + m = re.match( + r"\s*" + directiveName + r"\s*\((.*\w.*)\)\s*$", line, re.IGNORECASE + ) if m: argStr = m.groups()[0] else: @@ -58,16 +91,16 @@ def parseArgsFirstRest(directiveName,line): elif len(args) == 1: returnArgs = [args[0]] else: - returnArgs = [args[0],','.join(args[1:])] - + returnArgs = [args[0], ",".join(args[1:])] + return returnArgs -def parseArgsFirstSecondRest(directiveName,line): +def parseArgsFirstSecondRest(directiveName, line): """If the @-directive must have at least two arguments, parse into first, second, and rest strings. Added for assertAssociated. """ - args1 = parseArgsFirstRest(directiveName,line) + args1 = parseArgsFirstRest(directiveName, line) returnArgs = None @@ -75,33 +108,40 @@ def parseArgsFirstSecondRest(directiveName,line): if len(args1) == 1: returnArgs = args1 elif len(args1) == 2: - args2 = parseArgsFirstRest('',args1[1]) + args2 = parseArgsFirstRest("", args1[1]) returnArgs = [args1[0]] + args2 elif len(args1) == 3: - print(-999,'parseArgsFirstSecondRest::error!') + print(-999, "parseArgsFirstSecondRest::error!") returnArgs = None return returnArgs def getSelfObjectName(line): - m = re.match(r'\s*subroutine\s+\w*\s*\(\s*(\w+)\s*(,\s*\w+\s*)*\)\s*$', line, re.IGNORECASE) + m = re.match( + r"\s*subroutine\s+\w*\s*\(\s*(\w+)\s*(,\s*\w+\s*)*\)\s*$", line, re.IGNORECASE + ) if m: return m.groups()[0] else: return m + def getTypeName(line): - m = re.match(r'\s*type(.*::\s*|\s+)(\w*)\s*$', line, re.IGNORECASE) + m = re.match(r"\s*type(.*::\s*|\s+)(\w*)\s*$", line, re.IGNORECASE) return m.groups()[1] - -class Action(): + + +class Action: def apply(self, line): m = self.match(line) - if m: self.action(m, line) + if m: + self.action(m, line) return m -#------------------ + +# ------------------ + # If parser is in the state of looking for a test method name, then # check the current line to see if it defines a subroutine. @@ -111,17 +151,21 @@ def __init__(self, parser): self.parser = parser def match(self, line): - if (self.parser.looking_for_test_name): - m = re.match(r'\s*subroutine\s+(\w*)\s*(\([\w\s,]*\))?\s*(!.*)*$', line, re.IGNORECASE) + if self.parser.looking_for_test_name: + m = re.match( + r"\s*subroutine\s+(\w*)\s*(\([\w\s,]*\))?\s*(!.*)*$", + line, + re.IGNORECASE, + ) return m else: return False - + def action(self, m, line): - self.parser.current_method['name'] = m.groups()[0] + self.parser.current_method["name"] = m.groups()[0] dummyArgument = getSelfObjectName(line) if dummyArgument: - self.parser.current_method['selfObjectName'] = dummyArgument + self.parser.current_method["selfObjectName"] = dummyArgument self.parser.looking_for_test_name = False self.parser.userTestMethods.append(self.parser.current_method) self.parser.outputFile.write(line) @@ -132,107 +176,131 @@ def action(self, m, line): class AtTest(Action): def __init__(self, parser): self.parser = parser - self.keyword = '@test' + self.keyword = "@test" def match(self, line): - m = re.match(r'\s*'+self.keyword+r'(\s*(\(.*\))?\s*$)', line, re.IGNORECASE) + m = re.match(r"\s*" + self.keyword + r"(\s*(\(.*\))?\s*$)", line, re.IGNORECASE) return m def action(self, m, line): - options = re.match(r'\s*'+self.keyword+r'\s*\((.*)\)\s*$', line, re.IGNORECASE) + options = re.match( + r"\s*" + self.keyword + r"\s*\((.*)\)\s*$", line, re.IGNORECASE + ) self.parser.current_method = {} if options: - - npesOption = re.search(r'npes\s*=\s*\[([0-9,\s]+)\]', options.groups()[0], re.IGNORECASE) + npesOption = re.search( + r"npes\s*=\s*\[([0-9,\s]+)\]", options.groups()[0], re.IGNORECASE + ) if npesOption: npesString = npesOption.groups()[0] - npes = list(map(int, npesString.split(','))) - self.parser.current_method['npRequests'] = npes - - #ifdef is optional - matchIfdef = re.match(r'.*ifdef\s*=\s*(\w+)', options.groups()[0], re.IGNORECASE) - if matchIfdef: + npes = list(map(int, npesString.split(","))) + self.parser.current_method["npRequests"] = npes + + # ifdef is optional + matchIfdef = re.match( + r".*ifdef\s*=\s*(\w+)", options.groups()[0], re.IGNORECASE + ) + if matchIfdef: ifdef = matchIfdef.groups()[0] - self.parser.current_method['ifdef'] = ifdef + self.parser.current_method["ifdef"] = ifdef - matchIfndef = re.match(r'.*ifndef\s*=\s*(\w+)', options.groups()[0], re.IGNORECASE) - if matchIfndef: + matchIfndef = re.match( + r".*ifndef\s*=\s*(\w+)", options.groups()[0], re.IGNORECASE + ) + if matchIfndef: ifndef = matchIfndef.groups()[0] - self.parser.current_method['ifndef'] = ifndef + self.parser.current_method["ifndef"] = ifndef - matchType = re.match(r'.*type\s*=\s*(\w+)', options.groups()[0], re.IGNORECASE) + matchType = re.match( + r".*type\s*=\s*(\w+)", options.groups()[0], re.IGNORECASE + ) if matchType: - self.parser.current_method['type'] = matchType.groups()[0] + self.parser.current_method["type"] = matchType.groups()[0] - paramOption = re.search(r'testParameters\s*=\s*[{](.*)[}]', options.groups()[0], re.IGNORECASE) + paramOption = re.search( + r"testParameters\s*=\s*[{](.*)[}]", options.groups()[0], re.IGNORECASE + ) if paramOption: paramExpr = paramOption.groups()[0] - self.parser.current_method['testParameters'] = paramExpr + self.parser.current_method["testParameters"] = paramExpr - casesOption = re.search(r'cases\s*=\s*(\[[0-9,\s]+\])', options.groups()[0], re.IGNORECASE) + casesOption = re.search( + r"cases\s*=\s*(\[[0-9,\s]+\])", options.groups()[0], re.IGNORECASE + ) if casesOption: - self.parser.current_method['cases'] = casesOption.groups()[0] + self.parser.current_method["cases"] = casesOption.groups()[0] - -# nextLine = self.parser.nextLine() + # nextLine = self.parser.nextLine() self.parser.looking_for_test_name = True -# method['name'] = getSubroutineName(nextLine) + # method['name'] = getSubroutineName(nextLine) # save "self" name for use with @mpiAssert -# self.parser.currentSelfObjectName = getSelfObjectName(nextLine) + # self.parser.currentSelfObjectName = getSelfObjectName(nextLine) # save "self" name for use with @mpiAssert -# dummyArgument = getSelfObjectName(nextLine) -# if dummyArgument: -# self.parser.current_method['selfObjectName'] = dummyArgument + # dummyArgument = getSelfObjectName(nextLine) + # if dummyArgument: + # self.parser.current_method['selfObjectName'] = dummyArgument -# self.parser.userTestMethods.append(method) -# self.parser.current_method = method # for subsequent annotations + # self.parser.userTestMethods.append(method) + # self.parser.current_method = method # for subsequent annotations self.parser.commentLine(line) + + # self.parser.outputFile.write(nextLine) -#------------------ + +# ------------------ # deprecated - should now just use @test # @mpitest class AtMpiTest(AtTest): def __init__(self, parser): self.parser = parser - self.keyword = '@mpitest' + self.keyword = "@mpitest" + -# @testcase +# @testcase class AtTestCase(Action): def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*@testcase\s*(|\(.*\))\s*$', line, re.IGNORECASE) + m = re.match(r"\s*@testcase\s*(|\(.*\))\s*$", line, re.IGNORECASE) return m - + def action(self, m, line): - options = re.match(r'\s*@testcase\s*\((.*)\)\s*$', line, re.IGNORECASE) + options = re.match(r"\s*@testcase\s*\((.*)\)\s*$", line, re.IGNORECASE) if options: - value = re.search(r'constructor\s*=\s*(\w*)', options.groups()[0], re.IGNORECASE) + value = re.search( + r"constructor\s*=\s*(\w*)", options.groups()[0], re.IGNORECASE + ) if value: - self.parser.userTestCase['constructor'] = value.groups()[0] + self.parser.userTestCase["constructor"] = value.groups()[0] - value = re.search(r'npes\s*=\s*\[([0-9,\s]+)\]', options.groups()[0], re.IGNORECASE) + value = re.search( + r"npes\s*=\s*\[([0-9,\s]+)\]", options.groups()[0], re.IGNORECASE + ) if value: npesString = value.groups()[0] - npes = list(map(int,npesString.split(','))) - self.parser.userTestCase['npRequests'] = npes + npes = list(map(int, npesString.split(","))) + self.parser.userTestCase["npRequests"] = npes - value = re.search(r'cases\s*=\s*(\[[0-9,\s]+\])', options.groups()[0], re.IGNORECASE) + value = re.search( + r"cases\s*=\s*(\[[0-9,\s]+\])", options.groups()[0], re.IGNORECASE + ) if value: cases = value.groups()[0] - self.parser.userTestCase['cases'] = cases + self.parser.userTestCase["cases"] = cases - value = re.search(r'testParameters\s*=\s*[{](.*)[}]', options.groups()[0], re.IGNORECASE) + value = re.search( + r"testParameters\s*=\s*[{](.*)[}]", options.groups()[0], re.IGNORECASE + ) if value: paramExpr = value.groups()[0] - self.parser.userTestCase['testParameters'] = paramExpr + self.parser.userTestCase["testParameters"] = paramExpr nextLine = self.parser.nextLine() - self.parser.userTestCase['type']=getTypeName(nextLine) + self.parser.userTestCase["type"] = getTypeName(nextLine) self.parser.commentLine(line) self.parser.outputFile.write(nextLine) @@ -240,14 +308,19 @@ def action(self, m, line): class AtSuite(Action): def __init__(self, parser): self.parser = parser + def match(self, line): nameRe = r"'\w+'|" + r"""\w+""" - m = re.match(r"\s*@suite\s*\(\s*name\s*=\s*("+nameRe+r")\s*\)\s*$", line, re.IGNORECASE) + m = re.match( + r"\s*@suite\s*\(\s*name\s*=\s*(" + nameRe + r")\s*\)\s*$", + line, + re.IGNORECASE, + ) return m def action(self, m, line): - self.parser.suiteName=m.groups()[0][1:-1] - self.parser.wrapModuleName = 'Wrap' + self.parser.suiteName + self.parser.suiteName = m.groups()[0][1:-1] + self.parser.wrapModuleName = "Wrap" + self.parser.suiteName class AtBegin(Action): @@ -255,34 +328,33 @@ def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*module\s+(\w*)\s*$', line, re.IGNORECASE) + m = re.match(r"\s*module\s+(\w*)\s*$", line, re.IGNORECASE) return m def action(self, m, line): self.parser.outputFile.write(line) self.parser.userModuleName = m.groups()[0] - self.parser.wrapModuleName = 'Wrap' + self.parser.userModuleName + self.parser.wrapModuleName = "Wrap" + self.parser.userModuleName if self.parser.suiteName: return None self.parser.defaultSuiteName = self.parser.userModuleName + "_suite" - class AtAssert(Action): def __init__(self, parser): self.parser = parser - self.assert_variants = '|'.join(assert_operands.keys()) + self.assert_variants = "|".join(assert_operands.keys()) def match(self, line): -# m = re.match('\s*@assert('+assertVariants+')\s*\\((.*\w.*)\\)\s*$', line, re.IGNORECASE) - pattern = r'\s*@assert(' + self.assert_variants + r')\s*\((.*)\)\s*$' + # m = re.match('\s*@assert('+assertVariants+')\s*\\((.*\w.*)\\)\s*$', line, re.IGNORECASE) + pattern = r"\s*@assert(" + self.assert_variants + r")\s*\((.*)\)\s*$" match = re.match(pattern, line, re.IGNORECASE) if match: - num_operands = match.group(2).count(',') + num_operands = match.group(2).count(",") # The test is required to solve the fence post/panel problem. # In particular isspace returns false if the string is empty. if (len(match.group(2)) > 0) and not match.group(2).isspace(): @@ -302,39 +374,71 @@ def appendSourceLocation(self, fileHandle, fileName, lineNumber): def action(self, match, line): parser = self.parser - - parser.outputFile.write(cppSetLineAndFile(parser.currentLineNumber, - parser.fileName)) + + # Reconstitute logical assertion in case of Fortran continuation (&) + # Note: nextLine() already handles continuation, so this loop typically won't execute. + # Kept for safety if action is called directly with a raw line. + logical_line = line.rstrip("\n") + while logical_line.rstrip().endswith("&"): + amp_index = logical_line.rstrip().rfind("&") + # Keep everything before the '&', preserving whitespace + before_amp = logical_line[:amp_index] + # Read and attach the next source line + next_line = parser.inputFile.readline() + if not next_line: + break # EOF + parser.currentLineNumber += 1 + # Remove leading whitespace up to and including a leading '&' + # but preserve any whitespace after the '&' + after_amp = next_line.lstrip() + if after_amp.startswith("&"): + after_amp = after_amp[1:] # Remove only the '&', keep spaces after it + logical_line = before_amp + after_amp.rstrip("\n") + + # Now match the logical_line instead of just 'line' + match = self.match(logical_line) + if not match: + raise MyError(f"Could not parse logical assertion: {logical_line}") + + parser.outputFile.write( + cppSetLineAndFile(parser.currentLineNumber, parser.fileName) + ) fragment = " call assert{}({}" if (len(match.group(2)) > 0) and not match.group(2).isspace(): fragment += ", " fragment += "&\n" parser.outputFile.write(fragment.format(match.group(1), match.group(2))) - self.appendSourceLocation(parser.outputFile, - parser.fileName, - parser.currentLineNumber) + self.appendSourceLocation( + parser.outputFile, parser.fileName, parser.currentLineNumber + ) parser.outputFile.write(" )\n") parser.outputFile.write(" if (anyExceptions()) return\n") - parser.outputFile.write(cppSetLineAndFile(parser.currentLineNumber + 1, - parser.fileName)) + parser.outputFile.write( + cppSetLineAndFile(parser.currentLineNumber + 1, parser.fileName) + ) + class AtAssertAssociated(Action): - def __init__(self,parser): + def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*@assertassociated\s*\((.*\w.*)\)\s*$', line, re.IGNORECASE) + m = re.match(r"\s*@assertassociated\s*\((.*\w.*)\)\s*$", line, re.IGNORECASE) if not m: - m = re.match( \ - r'\s*@assertassociated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assertassociated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$", + line, + re.IGNORECASE, + ) # How to get both (a,b) and (a,b,c) to match? if not m: - m = re.match( \ - r'\s*@assertassociated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assertassociated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$", + line, + re.IGNORECASE, + ) return m def appendSourceLocation(self, fileHandle, fileName, lineNumber): @@ -346,53 +450,74 @@ def action(self, m, line): p = self.parser # args = parseArgsFirstRest('@assertassociated',line) - args = parseArgsFirstSecondRest('@assertassociated',line) + args = parseArgsFirstSecondRest("@assertassociated", line) p.outputFile.write(cppSetLineAndFile(p.currentLineNumber, p.fileName)) if len(args) > 1: - if re.match('.*message=.*',args[1],re.IGNORECASE): - p.outputFile.write(" call assertTrue(associated(" + args[0] + "), " + args[1] + ", &\n") + if re.match(".*message=.*", args[1], re.IGNORECASE): + p.outputFile.write( + " call assertTrue(associated(" + + args[0] + + "), " + + args[1] + + ", &\n" + ) elif len(args) > 2: - p.outputFile.write(" call assertTrue(associated(" + args[0] + "," + args[1] + "), " + args[2] + ", &\n") + p.outputFile.write( + " call assertTrue(associated(" + + args[0] + + "," + + args[1] + + "), " + + args[2] + + ", &\n" + ) else: - p.outputFile.write(" call assertTrue(associated(" + args[0] + "," + args[1] + "), &\n") + p.outputFile.write( + " call assertTrue(associated(" + args[0] + "," + args[1] + "), &\n" + ) else: p.outputFile.write(" call assertTrue(associated(" + args[0] + "), &\n") self.appendSourceLocation(p.outputFile, p.fileName, p.currentLineNumber) p.outputFile.write(" )\n") p.outputFile.write(" if (anyExceptions()) return\n") - p.outputFile.write(cppSetLineAndFile(p.currentLineNumber+1, p.fileName)) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber + 1, p.fileName)) class AtAssertNotAssociated(Action): - def __init__(self,parser): + def __init__(self, parser): self.parser = parser - self.name='@assertnotassociated' + self.name = "@assertnotassociated" def match(self, line): - m = re.match(r'\s*@assert(not|un)associated\s*\((.*\w.*)\)\s*$', line, re.IGNORECASE) + m = re.match( + r"\s*@assert(not|un)associated\s*\((.*\w.*)\)\s*$", line, re.IGNORECASE + ) if m: - self.name='@assert'+m.groups()[0]+'associated' + self.name = "@assert" + m.groups()[0] + "associated" else: - self.name='@assertnotassociated' + self.name = "@assertnotassociated" if not m: - m = re.match( \ - r'\s*@assert(not|un)associated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assert(not|un)associated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$", + line, + re.IGNORECASE, + ) # How to get both (a,b) and (a,b,c) to match? if not m: - m = re.match( \ - r'\s*@assert(not|un)associated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assert(not|un)associated\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$", + line, + re.IGNORECASE, + ) if m: - self.name='@assert'+m.groups()[0]+'associated' + self.name = "@assert" + m.groups()[0] + "associated" else: - self.name='@assertnotassociated' + self.name = "@assertnotassociated" - return m def appendSourceLocation(self, fileHandle, fileName, lineNumber): @@ -403,44 +528,69 @@ def appendSourceLocation(self, fileHandle, fileName, lineNumber): def action(self, m, line): p = self.parser - #-- args = parseArgsFirstRest('@assertassociated',line) - #ok args = parseArgsFirstSecondRest('@assertassociated',line) - args = parseArgsFirstSecondRest(self.name,line) + # -- args = parseArgsFirstRest('@assertassociated',line) + # ok args = parseArgsFirstSecondRest('@assertassociated',line) + args = parseArgsFirstSecondRest(self.name, line) p.outputFile.write(cppSetLineAndFile(p.currentLineNumber, p.fileName)) if len(args) > 1: - if re.match('.*message=.*',args[1],re.IGNORECASE): - p.outputFile.write(" call assertFalse(associated(" + args[0] + "), " + args[1] + ", &\n") + if re.match(".*message=.*", args[1], re.IGNORECASE): + p.outputFile.write( + " call assertFalse(associated(" + + args[0] + + "), " + + args[1] + + ", &\n" + ) elif len(args) > 2: - p.outputFile.write(" call assertFalse(associated(" + args[0] + "," + args[1] + "), " + args[2] + ", &\n") + p.outputFile.write( + " call assertFalse(associated(" + + args[0] + + "," + + args[1] + + "), " + + args[2] + + ", &\n" + ) else: - p.outputFile.write(" call assertFalse(associated(" + args[0] + "," + args[1] + "), &\n") + p.outputFile.write( + " call assertFalse(associated(" + + args[0] + + "," + + args[1] + + "), &\n" + ) else: p.outputFile.write(" call assertFalse(associated(" + args[0] + "), &\n") self.appendSourceLocation(p.outputFile, p.fileName, p.currentLineNumber) p.outputFile.write(" )\n") p.outputFile.write(" if (anyExceptions()) return\n") - p.outputFile.write(cppSetLineAndFile(p.currentLineNumber+1, p.fileName)) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber + 1, p.fileName)) class AtAssertEqualUserDefined(Action): """Convenience directive replacing (a,b) with a call to assertTrue(a==b) and an error message, if none is provided when invoked. """ - def __init__(self,parser): + + def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match( \ - r'\s*@assertequaluserdefined\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assertequaluserdefined\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$", + line, + re.IGNORECASE, + ) # How to get both (a,b) and (a,b,c) to match? if not m: - m = re.match( \ - r'\s*@assertequaluserdefined\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$', \ - line, re.IGNORECASE) - + m = re.match( + r"\s*@assertequaluserdefined\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$", + line, + re.IGNORECASE, + ) + return m def appendSourceLocation(self, fileHandle, fileName, lineNumber): @@ -451,41 +601,56 @@ def appendSourceLocation(self, fileHandle, fileName, lineNumber): def action(self, m, line): p = self.parser - args = parseArgsFirstSecondRest('@assertequaluserdefined',line) - + args = parseArgsFirstSecondRest("@assertequaluserdefined", line) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber, p.fileName)) if len(args) > 2: - p.outputFile.write(" call assertTrue(" \ - + args[0] + "==" + args[1] + ", " + args[2] + ", &\n") + p.outputFile.write( + " call assertTrue(" + + args[0] + + "==" + + args[1] + + ", " + + args[2] + + ", &\n" + ) else: - p.outputFile.write(" call assertTrue(" \ - + args[0] + "==" + args[1] + ", &\n") - if not re.match('.*message=.*',line,re.IGNORECASE): - p.outputFile.write(" & message='<" + args[0] + "> not equal to <" + args[1] + ">', &\n") + p.outputFile.write( + " call assertTrue(" + args[0] + "==" + args[1] + ", &\n" + ) + if not re.match(".*message=.*", line, re.IGNORECASE): + p.outputFile.write( + " & message='<" + args[0] + "> not equal to <" + args[1] + ">', &\n" + ) self.appendSourceLocation(p.outputFile, p.fileName, p.currentLineNumber) p.outputFile.write(" )\n") p.outputFile.write(" if (anyExceptions()) return\n") - p.outputFile.write(cppSetLineAndFile(p.currentLineNumber+1, p.fileName)) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber + 1, p.fileName)) class AtAssertEquivalent(Action): """Convenience directive replacing (a,b) with a call to assertTrue(a.eqv.b) and an error message, if none is provided when invoked. """ - def __init__(self,parser): + + def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match( \ - r'\s*@assertequivalent\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$', \ - line, re.IGNORECASE) + m = re.match( + r"\s*@assertequivalent\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*),(.*\w*.*))\)\s*$", + line, + re.IGNORECASE, + ) # How to get both (a,b) and (a,b,c) to match? if not m: - m = re.match( \ - r'\s*@assertequivalent\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$', \ - line, re.IGNORECASE) - + m = re.match( + r"\s*@assertequivalent\s*\((\s*([^,]*\w.*),\s*([^,]*\w.*))\)\s*$", + line, + re.IGNORECASE, + ) + return m def appendSourceLocation(self, fileHandle, fileName, lineNumber): @@ -496,34 +661,44 @@ def appendSourceLocation(self, fileHandle, fileName, lineNumber): def action(self, m, line): p = self.parser - args = parseArgsFirstSecondRest('@assertequivalent',line) - + args = parseArgsFirstSecondRest("@assertequivalent", line) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber, p.fileName)) if len(args) > 2: - p.outputFile.write(" call assertTrue(" \ - + args[0] + ".eqv." + args[1] + ", " + args[2] + ", &\n") + p.outputFile.write( + " call assertTrue(" + + args[0] + + ".eqv." + + args[1] + + ", " + + args[2] + + ", &\n" + ) else: - p.outputFile.write(" call assertTrue(" \ - + args[0] + ".eqv." + args[1] + ", &\n") - if not re.match('.*message=.*',line,re.IGNORECASE): - p.outputFile.write(" & message='<" + args[0] + "> not equal to <" + args[1] + ">', &\n") + p.outputFile.write( + " call assertTrue(" + args[0] + ".eqv." + args[1] + ", &\n" + ) + if not re.match(".*message=.*", line, re.IGNORECASE): + p.outputFile.write( + " & message='<" + args[0] + "> not equal to <" + args[1] + ">', &\n" + ) self.appendSourceLocation(p.outputFile, p.fileName, p.currentLineNumber) p.outputFile.write(" )\n") p.outputFile.write(" if (anyExceptions()) return\n") - p.outputFile.write(cppSetLineAndFile(p.currentLineNumber+1, p.fileName)) - + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber + 1, p.fileName)) + class AtMpiAssert(Action): def __init__(self, parser): self.parser = parser - self.assert_variants = '|'.join(assert_operands.keys()) + self.assert_variants = "|".join(assert_operands.keys()) def match(self, line): - pattern = r'\s*@mpiassert(' + self.assert_variants + r')\s*\((.*\w.*)\)\s*$' + pattern = r"\s*@mpiassert(" + self.assert_variants + r")\s*\((.*\w.*)\)\s*$" match = re.match(pattern, line, re.IGNORECASE) if match: - num_operands = match.group(2).count(',') + num_operands = match.group(2).count(",") # The test is required to solve the fence post/panel problem. # In particular isspace returns false if the string is empty. if (len(match.group(2)) > 0) and not match.group(2).isspace(): @@ -543,75 +718,86 @@ def appendSourceLocation(self, fileHandle, fileName, lineNumber): def action(self, m, line): p = self.parser - + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber, p.fileName)) - p.outputFile.write(" call assert"+m.groups()[0]+"(" + m.groups()[1] + ", &\n") + p.outputFile.write( + " call assert" + m.groups()[0] + "(" + m.groups()[1] + ", &\n" + ) self.appendSourceLocation(p.outputFile, p.fileName, p.currentLineNumber) p.outputFile.write(" )\n") # 'this' object may not exist if test is commented out. - if hasattr(p,'currentSelfObjectName'): - p.outputFile.write(" if (anyExceptions("+p.currentSelfObjectName+"%context)) return\n") - p.outputFile.write(cppSetLineAndFile(p.currentLineNumber+1, p.fileName)) + if hasattr(p, "currentSelfObjectName"): + p.outputFile.write( + " if (anyExceptions(" + p.currentSelfObjectName + "%context)) return\n" + ) + p.outputFile.write(cppSetLineAndFile(p.currentLineNumber + 1, p.fileName)) + class AtBefore(Action): def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*@before\s*$', line, re.IGNORECASE) - return m + m = re.match(r"\s*@before\s*$", line, re.IGNORECASE) + return m def action(self, m, line): nextLine = self.parser.nextLine() - self.parser.userTestCase['setUp'] = getSubroutineName(nextLine) + self.parser.userTestCase["setUp"] = getSubroutineName(nextLine) self.parser.commentLine(line) self.parser.outputFile.write(nextLine) + class AtAfter(Action): def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*@after\s*$', line, re.IGNORECASE) - return m + m = re.match(r"\s*@after\s*$", line, re.IGNORECASE) + return m def action(self, m, line): nextLine = self.parser.nextLine() - self.parser.userTestCase['tearDown'] = getSubroutineName(nextLine) + self.parser.userTestCase["tearDown"] = getSubroutineName(nextLine) self.parser.commentLine(line) self.parser.outputFile.write(nextLine) + class AtTestParameter(Action): def __init__(self, parser): self.parser = parser def match(self, line): - m = re.match(r'\s*@testParameter\s*(|.*)$', line, re.IGNORECASE) + m = re.match(r"\s*@testParameter\s*(|.*)$", line, re.IGNORECASE) return m def action(self, m, line): - options = re.match(r'\s*@testParameter\s*\((.*)\)\s*$', line, re.IGNORECASE) + options = re.match(r"\s*@testParameter\s*\((.*)\)\s*$", line, re.IGNORECASE) self.parser.commentLine(line) nextLine = self.parser.nextLine() - if not 'testParameterType' in self.parser.userTestCase: - self.parser.userTestCase['testParameterType'] = getTypeName(nextLine) + if not "testParameterType" in self.parser.userTestCase: + self.parser.userTestCase["testParameterType"] = getTypeName(nextLine) self.parser.outputFile.write(nextLine) if options: - value = re.search(r'constructor\s*=\s*(\w*)', options.groups()[0], re.IGNORECASE) + value = re.search( + r"constructor\s*=\s*(\w*)", options.groups()[0], re.IGNORECASE + ) if value: - self.parser.userTestCase['testParameterConstructor'] = value.groups()[0] + self.parser.userTestCase["testParameterConstructor"] = value.groups()[0] else: - self.parser.userTestCase['testParameterConstructor'] = self.parser.userTestCase['testParameterType'] + self.parser.userTestCase["testParameterConstructor"] = ( + self.parser.userTestCase["testParameterType"] + ) # @disable class AtDisable(Action): def __init__(self, parser): self.parser = parser - self.keyword = '@disable' + self.keyword = "@disable" def match(self, line): nameRe = r"'\w+'|" + r"""\w+""" @@ -619,38 +805,40 @@ def match(self, line): return m def action(self, m, line): - self.parser.current_method['disable'] = True + self.parser.current_method["disable"] = True self.parser.commentLine(line) - -class Parser(): + + +class Parser: def __init__(self, inputFileName, outputFileName): def getBaseName(fileName): from os.path import basename, splitext + base = basename(fileName) return splitext(base)[0] self.fileName = inputFileName - self.inputFile = open(inputFileName, 'r') - self.outputFile = open(outputFileName, 'w') + self.inputFile = open(inputFileName, "r") + self.outputFile = open(outputFileName, "w") self.defaultSuiteName = getBaseName(inputFileName) + "_suite" - self.suiteName = '' + self.suiteName = "" self.currentLineNumber = 0 - self.userModuleName = '' # if any + self.userModuleName = "" # if any self.userTestCase = {} - self.userTestCase['setUpMethod'] = '' - self.userTestCase['tearDownMethod'] = '' - self.userTestCase['defaultTestParameterNpes'] = [] # is MPI if not empty - self.userTestCase['defaultTestParametersExpr'] = '' - self.userTestCase['defaultTestParameterCases'] = [] + self.userTestCase["setUpMethod"] = "" + self.userTestCase["tearDownMethod"] = "" + self.userTestCase["defaultTestParameterNpes"] = [] # is MPI if not empty + self.userTestCase["defaultTestParametersExpr"] = "" + self.userTestCase["defaultTestParameterCases"] = [] - self.userTestMethods = [] # each entry is a dictionary + self.userTestMethods = [] # each entry is a dictionary self.wrapModuleName = "Wrap" + getBaseName(inputFileName) self.currentLineNumber = 0 - self.actions=[] + self.actions = [] self.actions.append(AtTest(self)) self.actions.append(IsTestMethod(self)) self.actions.append(AtMpiTest(self)) @@ -661,318 +849,408 @@ def getBaseName(fileName): self.actions.append(AtAssert(self)) self.actions.append(AtAssertAssociated(self)) -# self.actions.append(AtAssertAssociatedWith(self)) + # self.actions.append(AtAssertAssociatedWith(self)) self.actions.append(AtAssertNotAssociated(self)) -# self.actions.append(AtAssertNotAssociatedWith(self)) + # self.actions.append(AtAssertNotAssociatedWith(self)) self.actions.append(AtAssertEqualUserDefined(self)) self.actions.append(AtAssertEquivalent(self)) - + self.actions.append(AtMpiAssert(self)) self.actions.append(AtBefore(self)) self.actions.append(AtAfter(self)) self.actions.append(AtTestParameter(self)) - def commentLine(self, line): - self.outputFile.write(re.sub('@','!@',line)) + self.outputFile.write(re.sub("@", "!@", line)) def run(self): def parse(line): for action in self.actions: - if (action.apply(line)): return + if action.apply(line): + return self.outputFile.write(line) while True: line = self.nextLine() - if not line: break + if not line: + break parse(line) - if (not self.suiteName): + if not self.suiteName: self.suiteName = self.defaultSuiteName mname = self.userModuleName if mname: base = splitext(basename(self.fileName))[0] # As Fortran is not case-sensitive with module names, we use a case-insensitive match if mname.lower() != base.lower(): - raise Exception("pFUnit preprocessor: module name (" + mname + ") and file name (" + base + ") do not match (ignoring case).") - - if ('testParameterType' in self.userTestCase and (not 'constructor' in self.userTestCase)): - self.userTestCase['constructor'] = self.userTestCase['testParameterType'] + raise Exception( + "pFUnit preprocessor: module name (" + + mname + + ") and file name (" + + base + + ") do not match (ignoring case)." + ) + + if "testParameterType" in self.userTestCase and ( + not "constructor" in self.userTestCase + ): + self.userTestCase["constructor"] = self.userTestCase["testParameterType"] self.makeWrapperModule() def isComment(self, line): - return re.match(r'\s*(!.*|)$', line) + return re.match(r"\s*(!.*|)$", line) def nextLine(self): + # Loop until we get a non-comment, non-blank (possibly continued) line + logical_line = "" + start_line_number = None while True: self.currentLineNumber += 1 line = self.inputFile.readline() - if not line: break - if (self.isComment(line)): + if not line: + break + if self.isComment(line): self.outputFile.write(line) - pass + continue else: + # Start tracking line number for multi-line error reporting + if start_line_number is None: + start_line_number = self.currentLineNumber + + candidate_line = line.rstrip("\r\n") + # New logic for multi-line Fortran with ampersand continuation: + while True: + # Fortran-style continuation, check trailing '&' before any comment + stripped = candidate_line.rstrip() + comment_pos = stripped.find("!") + amp_pos = stripped.rfind("&") + is_continued = ( + amp_pos != -1 + and (comment_pos == -1 or amp_pos < comment_pos) + and amp_pos == len(stripped) - 1 + ) + if is_continued: + # Keep everything before the '&', preserving whitespace + prefix = candidate_line[:amp_pos] + logical_line += prefix + # Get the next physical line + self.currentLineNumber += 1 + continuation = self.inputFile.readline() + if not continuation: + break + # Remove leading whitespace up to and including a leading '&' + # but preserve any whitespace after the '&' + tail = continuation.lstrip() + if tail.startswith("&"): + tail = tail[1:] # Remove only the '&', keep spaces after it + candidate_line = tail.rstrip("\r\n") + continue + else: + logical_line += candidate_line + break break - return line - + # Add newline back since readline() includes it and callers expect it + if logical_line: + logical_line += "\n" + return logical_line def printHeader(self): - self.outputFile.write('\n') - self.outputFile.write('module ' + self.wrapModuleName + '\n') - self.outputFile.write(' use FUnit\n') - if (self.userModuleName): self.outputFile.write(' use ' + self.userModuleName + '\n') - self.outputFile.write(' implicit none\n') - self.outputFile.write(' private\n\n') - - + self.outputFile.write("\n") + self.outputFile.write("module " + self.wrapModuleName + "\n") + self.outputFile.write(" use FUnit\n") + if self.userModuleName: + self.outputFile.write(" use " + self.userModuleName + "\n") + self.outputFile.write(" implicit none\n") + self.outputFile.write(" private\n\n") def printTail(self): - self.outputFile.write('\n') - self.outputFile.write('end module ' + self.wrapModuleName + '\n\n') + self.outputFile.write("\n") + self.outputFile.write("end module " + self.wrapModuleName + "\n\n") def printWrapUserTestCase(self): - self.outputFile.write(' public :: WrapUserTestCase\n') - self.outputFile.write(' public :: makeCustomTest\n') - self.outputFile.write(' type, extends(' + self.userTestCase['type'] + ') :: WrapUserTestCase\n') - self.outputFile.write(' procedure(userTestMethod), nopass, pointer :: testMethodPtr\n') - self.outputFile.write(' contains\n') - self.outputFile.write(' procedure :: runMethod\n') - self.outputFile.write(' end type WrapUserTestCase\n\n') - - self.outputFile.write(' abstract interface\n') - self.outputFile.write(' subroutine userTestMethod(this)\n') + self.outputFile.write(" public :: WrapUserTestCase\n") + self.outputFile.write(" public :: makeCustomTest\n") + self.outputFile.write( + " type, extends(" + self.userTestCase["type"] + ") :: WrapUserTestCase\n" + ) + self.outputFile.write( + " procedure(userTestMethod), nopass, pointer :: testMethodPtr\n" + ) + self.outputFile.write(" contains\n") + self.outputFile.write(" procedure :: runMethod\n") + self.outputFile.write(" end type WrapUserTestCase\n\n") + + self.outputFile.write(" abstract interface\n") + self.outputFile.write(" subroutine userTestMethod(this)\n") if self.userModuleName: - self.outputFile.write(' use ' + self.userModuleName + '\n') - if 'type' in self.userTestCase: - self.outputFile.write(' class (' + self.userTestCase['type'] + '), intent(inout) :: this\n') - self.outputFile.write(' end subroutine userTestMethod\n') - self.outputFile.write(' end interface\n\n') + self.outputFile.write(" use " + self.userModuleName + "\n") + if "type" in self.userTestCase: + self.outputFile.write( + " class (" + + self.userTestCase["type"] + + "), intent(inout) :: this\n" + ) + self.outputFile.write(" end subroutine userTestMethod\n") + self.outputFile.write(" end interface\n\n") def printRunMethod(self): - self.outputFile.write(' subroutine runMethod(this)\n') - self.outputFile.write(' class (WrapUserTestCase), intent(inout) :: this\n\n') - self.outputFile.write(' call this%testMethodPtr(this)\n') - self.outputFile.write(' end subroutine runMethod\n\n') + self.outputFile.write(" subroutine runMethod(this)\n") + self.outputFile.write( + " class (WrapUserTestCase), intent(inout) :: this\n\n" + ) + self.outputFile.write(" call this%testMethodPtr(this)\n") + self.outputFile.write(" end subroutine runMethod\n\n") - def printParameterHeader(self, type): - self.outputFile.write(' type (' + type + '), allocatable :: testParameters(:)\n') - self.outputFile.write(' type (' + type + ') :: testParameter\n') - self.outputFile.write(' integer :: iParam, iCase \n') - self.outputFile.write(' integer, allocatable :: cases(:) \n') - self.outputFile.write(' \n') - + self.outputFile.write( + " type (" + type + "), allocatable :: testParameters(:)\n" + ) + self.outputFile.write(" type (" + type + ") :: testParameter\n") + self.outputFile.write(" integer :: iParam, iCase \n") + self.outputFile.write(" integer, allocatable :: cases(:) \n") + self.outputFile.write(" \n") def printMakeSuite(self): - self.outputFile.write('function ' + self.suiteName + '() result(suite)\n') - self.outputFile.write(' use FUnit\n') - if (self.userModuleName): self.outputFile.write(' use ' + self.userModuleName + '\n') - self.outputFile.write(' use '+ self.wrapModuleName + '\n') - self.outputFile.write(' implicit none'+ '\n') - self.outputFile.write(' type (TestSuite) :: suite\n\n') - self.outputFile.write(' class (Test), allocatable :: t\n\n') + self.outputFile.write("function " + self.suiteName + "() result(suite)\n") + self.outputFile.write(" use FUnit\n") + if self.userModuleName: + self.outputFile.write(" use " + self.userModuleName + "\n") + self.outputFile.write(" use " + self.wrapModuleName + "\n") + self.outputFile.write(" implicit none" + "\n") + self.outputFile.write(" type (TestSuite) :: suite\n\n") + self.outputFile.write(" class (Test), allocatable :: t\n\n") if not self.userModuleName: for testMethod in self.userTestMethods: - if ('ifdef' in testMethod): - self.outputFile.write('#ifdef ' + testMethod['ifdef'] + '\n') - elif ('ifndef' in testMethod): - self.outputFile.write('#ifndef ' + testMethod['ifndef'] + '\n') - self.outputFile.write(' external ' + testMethod['name'] + '\n') - if ('ifdef' in testMethod or 'ifndef' in testMethod): - self.outputFile.write('#endif\n') - self.outputFile.write('\n') - if 'setUp' in self.userTestCase: - self.outputFile.write(' external ' + self.userTestCase['setUp'] + '\n') - if 'tearDown' in self.userTestCase: - self.outputFile.write(' external ' + self.userTestCase['tearDown'] + '\n') - self.outputFile.write('\n') - - if 'testParameterType' in self.userTestCase: - type = self.userTestCase['testParameterType'] + if "ifdef" in testMethod: + self.outputFile.write("#ifdef " + testMethod["ifdef"] + "\n") + elif "ifndef" in testMethod: + self.outputFile.write("#ifndef " + testMethod["ifndef"] + "\n") + self.outputFile.write(" external " + testMethod["name"] + "\n") + if "ifdef" in testMethod or "ifndef" in testMethod: + self.outputFile.write("#endif\n") + self.outputFile.write("\n") + if "setUp" in self.userTestCase: + self.outputFile.write( + " external " + self.userTestCase["setUp"] + "\n" + ) + if "tearDown" in self.userTestCase: + self.outputFile.write( + " external " + self.userTestCase["tearDown"] + "\n" + ) + self.outputFile.write("\n") + + if "testParameterType" in self.userTestCase: + type = self.userTestCase["testParameterType"] self.printParameterHeader(type) self.outputFile.write(" suite = TestSuite('" + self.suiteName + "')\n\n") for testMethod in self.userTestMethods: - if ('ifdef' in testMethod): - self.outputFile.write('#ifdef ' + testMethod['ifdef'] + '\n') - elif ('ifndef' in testMethod): - self.outputFile.write('#ifndef ' + testMethod['ifndef'] + '\n') - if 'type' in self.userTestCase: + if "ifdef" in testMethod: + self.outputFile.write("#ifdef " + testMethod["ifdef"] + "\n") + elif "ifndef" in testMethod: + self.outputFile.write("#ifndef " + testMethod["ifndef"] + "\n") + if "type" in self.userTestCase: self.addUserTestMethod(testMethod) else: - if 'npRequests' in testMethod: + if "npRequests" in testMethod: self.addMpiTestMethod(testMethod) - else: # vanilla + else: # vanilla self.addSimpleTestMethod(testMethod) - self.outputFile.write('\n') - if ('ifdef' in testMethod or 'ifndef' in testMethod): - self.outputFile.write('#endif\n') + self.outputFile.write("\n") + if "ifdef" in testMethod or "ifndef" in testMethod: + self.outputFile.write("#endif\n") - self.outputFile.write('\nend function ' + self.suiteName + '\n\n') + self.outputFile.write("\nend function " + self.suiteName + "\n\n") def addSimpleTestMethod(self, testMethod): - args = "'" + testMethod['name'] + "', &\n " + testMethod['name'] - if 'setUp' in testMethod: - args += ', ' + 'setUp='+testMethod['setUp'] - elif 'setUp' in self.userTestCase: - args += ', ' + 'setUp='+self.userTestCase['setUp'] - - if 'tearDown' in testMethod: - args += ', ' + 'tearDown='+testMethod['tearDown'] - elif 'tearDown' in self.userTestCase: - args += ', ' + 'tearDown='+self.userTestCase['tearDown'] - - if 'type' in testMethod: - type = testMethod['type'] + args = "'" + testMethod["name"] + "', &\n " + testMethod["name"] + if "setUp" in testMethod: + args += ", " + "setUp=" + testMethod["setUp"] + elif "setUp" in self.userTestCase: + args += ", " + "setUp=" + self.userTestCase["setUp"] + + if "tearDown" in testMethod: + args += ", " + "tearDown=" + testMethod["tearDown"] + elif "tearDown" in self.userTestCase: + args += ", " + "tearDown=" + self.userTestCase["tearDown"] + + if "type" in testMethod: + type = testMethod["type"] else: - type = 'TestMethod' + type = "TestMethod" - self.outputFile.write(' if(allocated(t)) deallocate(t)\n') - self.outputFile.write(' allocate(t, source=' + type + '(' + args + '))\n') - if ('disable' in testMethod): - self.outputFile.write(' call t%insert(Disable%type_name(),Disable)\n') - self.outputFile.write(' call suite%addTest(t)\n') + self.outputFile.write(" if(allocated(t)) deallocate(t)\n") + self.outputFile.write(" allocate(t, source=" + type + "(" + args + "))\n") + if "disable" in testMethod: + self.outputFile.write(" call t%insert(Disable%type_name(),Disable)\n") + self.outputFile.write(" call suite%addTest(t)\n") def addMpiTestMethod(self, testMethod): - for npes in testMethod['npRequests']: - args = "'" + testMethod['name'] + "', &\n " + testMethod['name'] + ", " + str(npes) - if 'setUp' in testMethod: - args += ', ' + testMethod['setUp'] - elif 'setUp' in self.userTestCase: - args += ', ' + self.userTestCase['setUp'] - - if 'tearDown' in testMethod: - args += ', ' + testMethod['tearDown'] - elif 'tearDown' in self.userTestCase: - args += ', ' + self.userTestCase['tearDown'] - - if 'type' in testMethod: - type = testMethod['type'] + for npes in testMethod["npRequests"]: + args = ( + "'" + + testMethod["name"] + + "', &\n " + + testMethod["name"] + + ", " + + str(npes) + ) + if "setUp" in testMethod: + args += ", " + testMethod["setUp"] + elif "setUp" in self.userTestCase: + args += ", " + self.userTestCase["setUp"] + + if "tearDown" in testMethod: + args += ", " + testMethod["tearDown"] + elif "tearDown" in self.userTestCase: + args += ", " + self.userTestCase["tearDown"] + + if "type" in testMethod: + type = testMethod["type"] else: - type = 'MpiTestMethod' - - self.outputFile.write(' if(allocated(t)) deallocate(t)\n') - self.outputFile.write(' allocate(t, source=' + type + '(' + args + '))\n') - if ('disable' in testMethod): - self.outputFile.write(' call t%insert(Disable%type_name(),Disable)\n') - self.outputFile.write(' call suite%addTest(t)\n') + type = "MpiTestMethod" + self.outputFile.write(" if(allocated(t)) deallocate(t)\n") + self.outputFile.write(" allocate(t, source=" + type + "(" + args + "))\n") + if "disable" in testMethod: + self.outputFile.write(" call t%insert(Disable%type_name(),Disable)\n") + self.outputFile.write(" call suite%addTest(t)\n") - def addUserTestMethod(self, testMethod): - args = "'" + testMethod['name'] + "', " + testMethod['name'] - if 'npRequests' in testMethod: - npRequests = testMethod['npRequests'] + args = "'" + testMethod["name"] + "', " + testMethod["name"] + if "npRequests" in testMethod: + npRequests = testMethod["npRequests"] else: - if 'npRequests' in self.userTestCase: - npRequests = self.userTestCase['npRequests'] + if "npRequests" in self.userTestCase: + npRequests = self.userTestCase["npRequests"] else: npRequests = [1] - if 'cases' in testMethod: - cases = testMethod['cases'] - elif 'cases' in self.userTestCase: - cases = self.userTestCase['cases'] - - testParameterArg = '' # unless - - if 'cases' in locals(): - testParameterArg = ', testParameter' - self.outputFile.write(' cases = ' + testMethod['cases'] + '\n') - self.outputFile.write(' testParameters = [(' + - self.userTestCase['testParameterConstructor'] + - '(cases(iCase)), iCase = 1, size(cases))]\n\n') - - if 'testParameterType' in self.userTestCase: - if 'testParameters' in testMethod: - testParameters = testMethod['testParameters'] - elif 'testParameters' in self.userTestCase: - testParameters = self.userTestCase['testParameters'] - - isMpiTestCase = 'npRequests' in self.userTestCase - isMpiTestCase = isMpiTestCase or any('npRequests' in testMethod for testMethod in self.userTestMethods) - - if 'testParameters' in locals(): - testParameterArg = ', testParameter' - self.outputFile.write(' testParameters = ' + testParameters + '\n\n') + if "cases" in testMethod: + cases = testMethod["cases"] + elif "cases" in self.userTestCase: + cases = self.userTestCase["cases"] + + testParameterArg = "" # unless + + if "cases" in locals(): + testParameterArg = ", testParameter" + self.outputFile.write(" cases = " + testMethod["cases"] + "\n") + self.outputFile.write( + " testParameters = [(" + + self.userTestCase["testParameterConstructor"] + + "(cases(iCase)), iCase = 1, size(cases))]\n\n" + ) + + if "testParameterType" in self.userTestCase: + if "testParameters" in testMethod: + testParameters = testMethod["testParameters"] + elif "testParameters" in self.userTestCase: + testParameters = self.userTestCase["testParameters"] + + isMpiTestCase = "npRequests" in self.userTestCase + isMpiTestCase = isMpiTestCase or any( + "npRequests" in testMethod for testMethod in self.userTestMethods + ) + + if "testParameters" in locals(): + testParameterArg = ", testParameter" + self.outputFile.write(" testParameters = " + testParameters + "\n\n") elif isMpiTestCase: - testParameterArg = ', testParameter' - + testParameterArg = ", testParameter" for npes in npRequests: - - if 'testParameters' in locals() or 'cases' in locals(): - self.outputFile.write(' do iParam = 1, size(testParameters)\n') - self.outputFile.write(' testParameter = testParameters(iParam)\n') + if "testParameters" in locals() or "cases" in locals(): + self.outputFile.write(" do iParam = 1, size(testParameters)\n") + self.outputFile.write(" testParameter = testParameters(iParam)\n") if isMpiTestCase: - self.outputFile.write(' call testParameter%setNumProcessesRequested(' + str(npes) + ')\n') - - self.outputFile.write(' call suite%addTest(makeCustomTest(' + - args + testParameterArg + '))\n') - if 'cases' in locals() or 'testParameters' in locals(): - self.outputFile.write(' end do\n') - - + self.outputFile.write( + " call testParameter%setNumProcessesRequested(" + + str(npes) + + ")\n" + ) + + self.outputFile.write( + " call suite%addTest(makeCustomTest(" + + args + + testParameterArg + + "))\n" + ) + if "cases" in locals() or "testParameters" in locals(): + self.outputFile.write(" end do\n") def printMakeCustomTest(self, isMpiTestCase): - args = 'methodName, testMethod' - declareArgs = ' type (WrapUserTestCase) :: aTest\n' - declareArgs += ' character(len=*), intent(in) :: methodName\n' - declareArgs += ' procedure(userTestMethod) :: testMethod\n' - - if 'testParameterType' in self.userTestCase: - args += ', testParameter' - declareArgs += ' type (' + self.userTestCase['testParameterType'] + '), intent(in) :: testParameter\n' - - self.outputFile.write(' function makeCustomTest(' + args + ') result(aTest)\n') + args = "methodName, testMethod" + declareArgs = " type (WrapUserTestCase) :: aTest\n" + declareArgs += " character(len=*), intent(in) :: methodName\n" + declareArgs += " procedure(userTestMethod) :: testMethod\n" + + if "testParameterType" in self.userTestCase: + args += ", testParameter" + declareArgs += ( + " type (" + + self.userTestCase["testParameterType"] + + "), intent(in) :: testParameter\n" + ) + + self.outputFile.write( + " function makeCustomTest(" + args + ") result(aTest)\n" + ) self.outputFile.write(declareArgs) - if 'constructor' in self.userTestCase: - if 'testParameterType' in self.userTestCase: - constructor = self.userTestCase['constructor'] + '(testParameter)' + if "constructor" in self.userTestCase: + if "testParameterType" in self.userTestCase: + constructor = self.userTestCase["constructor"] + "(testParameter)" else: - constructor = self.userTestCase['constructor'] + '()' - self.outputFile.write(' aTest%' + self.userTestCase['type'] + ' = ' + constructor + '\n\n') + constructor = self.userTestCase["constructor"] + "()" + self.outputFile.write( + " aTest%" + + self.userTestCase["type"] + + " = " + + constructor + + "\n\n" + ) - self.outputFile.write(' aTest%testMethodPtr => testMethod\n') - - self.outputFile.write('#ifdef INTEL_13\n') - self.outputFile.write(' p => aTest\n') - self.outputFile.write(' call p%setName(methodName)\n') - self.outputFile.write('#else\n') - self.outputFile.write(' call aTest%setName(methodName)\n') - self.outputFile.write('#endif\n') + self.outputFile.write(" aTest%testMethodPtr => testMethod\n") - if 'testParameterType' in self.userTestCase: - self.outputFile.write(' call aTest%setTestParameter(testParameter)\n') - + self.outputFile.write("#ifdef INTEL_13\n") + self.outputFile.write(" p => aTest\n") + self.outputFile.write(" call p%setName(methodName)\n") + self.outputFile.write("#else\n") + self.outputFile.write(" call aTest%setName(methodName)\n") + self.outputFile.write("#endif\n") - self.outputFile.write(' end function makeCustomTest\n') + if "testParameterType" in self.userTestCase: + self.outputFile.write(" call aTest%setTestParameter(testParameter)\n") + + self.outputFile.write(" end function makeCustomTest\n") def makeWrapperModule(self): - #----------------------------------------------------------- + # ----------------------------------------------------------- # ! Start here self.printHeader() - if 'type' in self.userTestCase: + if "type" in self.userTestCase: self.printWrapUserTestCase() - - self.outputFile.write('contains\n\n') - if 'type' in self.userTestCase: + self.outputFile.write("contains\n\n") + + if "type" in self.userTestCase: self.printRunMethod() - if 'type' in self.userTestCase: - isMpiTestCase = 'npRequests' in self.userTestCase - isMpiTestCase = isMpiTestCase or any('npRequests' in testMethod for testMethod in self.userTestMethods) - if isMpiTestCase and not 'testParameterType' in self.userTestCase: - self.userTestCase['testParameterType'] = 'MpiTestParameter' + if "type" in self.userTestCase: + isMpiTestCase = "npRequests" in self.userTestCase + isMpiTestCase = isMpiTestCase or any( + "npRequests" in testMethod for testMethod in self.userTestMethods + ) + if isMpiTestCase and not "testParameterType" in self.userTestCase: + self.userTestCase["testParameterType"] = "MpiTestParameter" self.printMakeCustomTest(isMpiTestCase) diff --git a/bin/funit/tests/parser_test_utils.py b/bin/funit/tests/parser_test_utils.py new file mode 100644 index 00000000..8371c759 --- /dev/null +++ b/bin/funit/tests/parser_test_utils.py @@ -0,0 +1,42 @@ +"""Shared test fixtures and mocks for parser tests.""" + +import sys + +sys.path.append("..") + +from funit.pFUnitParser import Parser + + +class MockWriter: + """Mock file writer that captures output lines.""" + + def __init__(self, parser): + self.parser = parser + + def write(self, line): + self.parser.outLines.append(line) + + +class MockParser(Parser): + """Mock parser for testing that doesn't require actual files.""" + + def __init__(self, lines): + self.saveLines = lines + self.lines = self.saveLines[:] + self.outputFile = MockWriter(self) + self.outLines = [] + self.userTestCase = {} + self.userTestMethods = [] + self.currentSelfObjectName = "" + + def nextLine(self): + while True: + line = self.lines.pop(0) + if self.isComment(line): + pass + else: + break + return line + + def reset(self): + self.lines = self.saveLines[:] diff --git a/bin/funit/tests/testParser.py b/bin/funit/tests/testParser.py deleted file mode 100644 index e75f09bd..00000000 --- a/bin/funit/tests/testParser.py +++ /dev/null @@ -1,590 +0,0 @@ -import unittest -import sys -sys.path.append('..') -from funit.pFUnitParser import * - -class MockWriter(): - def __init__(self, parser): - self.parser = parser - - def write(self, line): - self.parser.outLines.append(line) - -class MockParser(Parser): - def __init__(self, lines): - self.saveLines = lines - self.lines = self.saveLines[:] - self.outputFile = MockWriter(self) - self.outLines = [] - self.userTestCase = {} - self.userTestMethods = [] - self.currentSelfObjectName = '' - - def nextLine(self): - while True: - line = self.lines.pop(0) - if (self.isComment(line)): - pass - else: - break - return line - - def reset(self): - self.lines = self.saveLines[:] - -class TestParseLine(unittest.TestCase): - - def testCppSetLineAndFile(self): - self.assertEqual('#line 7 "foo"\n', cppSetLineAndFile(7, 'foo')) - self.assertEqual('#line 3 "bar"\n', cppSetLineAndFile(3, 'bar')) - - def testGetSubroutineName(self): - self.assertEqual('a', getSubroutineName('subroutine a()')) - self.assertEqual('abcd', getSubroutineName('subroutine abcd ()')) - - def testGetSelfObjectName(self): - self.assertEqual('b', getSelfObjectName('subroutine a(b)')) - self.assertEqual('bc', getSelfObjectName('subroutine a(bc)')) - self.assertEqual('bc', getSelfObjectName('subroutine a(bc,d)')) - self.assertEqual('bc', getSelfObjectName('subroutine a(bc, d)')) - self.assertEqual('bc', getSelfObjectName('subroutine a(bc ,d)')) - self.assertEqual('bc', getSelfObjectName('subroutine a(bc , d)')) - - - def testGetTypeName(self): - self.assertEqual('foo', getTypeName(' type :: foo')) - self.assertEqual('foo', getTypeName(' type, extends(something) :: foo')) - self.assertEqual('foo', getTypeName(' type, abstract :: foo')) - self.assertEqual('foo', getTypeName(' type, extends(something), abstract :: foo')) - - def testAtTest(self): - """Check that a line starting with '@test' is detected as an - annotation.""" - nextLine = 'subroutine myTest()\n' - parser = MockParser([nextLine]) - atTest = AtTest(parser) - is_test_method = IsTestMethod(parser) - - self.assertTrue(atTest.match('@test')) - self.assertFalse(atTest.match('! @test')) - self.assertTrue(atTest.match(' @test')) # leading space - self.assertTrue(atTest.match('@TEST')) # case insensitive - self.assertTrue(atTest.match('@Test')) # mixed case - self.assertFalse(atTest.match('@Testb')) # can't have trailing characters without whitespace - self.assertTrue(atTest.match('@Test (b)')) - self.assertTrue(atTest.match('@Test(b)')) - self.assertFalse(atTest.match('@Test b')) # next non-space character needs to be '(') - self.assertTrue(atTest.match('@Test(timeout=3.0)')) - self.assertFalse(atTest.match('! @Test')) - self.assertFalse(atTest.match('@assertTrue')) - self.assertTrue(atTest.match('@test(cases = [1,3,5])')) - - atTest.apply('@test\n') - is_test_method.apply(nextLine) - self.assertEqual('myTest', parser.userTestMethods[0]['name']) - self.assertEqual('!@test\n',parser.outLines[0]) - self.assertEqual(nextLine,parser.outLines[1]) - - def testAtTestNoParens(self): - """Check that test procedure with no parens is accepted.""" - nextLine = 'subroutine myTest ! and a comment \n' - parser = MockParser([nextLine]) - atTest = AtTest(parser) - is_test_method = IsTestMethod(parser) - - m = atTest.match('@test\n') - atTest.action(m,'@test\n') - is_test_method.apply(nextLine) - self.assertEqual('myTest', parser.userTestMethods[0]['name']) - self.assertEqual('!@test\n',parser.outLines[0]) - self.assertEqual(nextLine,parser.outLines[1]) - - @unittest.skip("I don't think this test can pass with the new multi-stage parsing") - def testAtTestFail(self): - """Check that useful error is sent if next line is not properly formatted.""" - - nextLine = 'subroutine myTest (] \n' # bad closing paren - parser = MockParser([nextLine]) - - with self.assertRaises(MyError): - atTest = AtTest(parser) - line = '@test' - m = atTest.match(line) - atTest.action(m, line) - - is_test_method = IsTestMethod(parser) - is_test_method.apply(nextLine) - - - def testAtTestSkipComment(self): - """Ignore comment lines between @test and subroutine foo().""" - nextLineA = '! ignore this line \n' - nextLineB = '\n' - nextLineC = 'subroutine myTestC()\n' - parser = MockParser([nextLineA,nextLineB,nextLineC]) - - atTest = AtTest(parser) - atTest.apply('@test\n') - is_test_method = IsTestMethod(parser) - is_test_method.apply(nextLineA) - is_test_method.apply(nextLineB) - is_test_method.apply(nextLineC) - self.assertEqual('myTestC', parser.userTestMethods[0]['name']) - self.assertEqual('!@test\n',parser.outLines[0]) - self.assertEqual(nextLineC,parser.outLines[1]) - - def testAtMpiTest(self): - """Check that a line starting with '@mpitest' is detected as an - annotation and that optional parameters are collected.""" - - nextLine = 'subroutine myTest(this)\n' - parser = MockParser([nextLine]) - atMpiTest = AtMpiTest(parser) - is_test_method = IsTestMethod(parser) - - line = '@mpitest(npes=[1])' - m = atMpiTest.match(line) - self.assertTrue(m) - atMpiTest.action(m,line) - is_test_method.apply(nextLine) - self.assertEqual([1], parser.userTestMethods[0]['npRequests']) - self.assertFalse('ifdef' in parser.userTestMethods[0]) - - # ignore leading space? - line = '@mpitest( npes=[1])' - parser.reset() - m = atMpiTest.match(line) - self.assertTrue(m) - atMpiTest.action(m,line) - is_test_method.apply(nextLine) - self.assertEqual([1], parser.userTestMethods[1]['npRequests']) - self.assertFalse('ifdef' in parser.userTestMethods[1]) - - line = '@mpitest(npes=[1, 2,3], ifdef=USE_MPI)' - parser.reset() - m = atMpiTest.match(line) - self.assertTrue(m) - atMpiTest.action(m,line) - is_test_method.apply(nextLine) - self.assertEqual([1,2,3], parser.userTestMethods[2]['npRequests']) - self.assertEqual('USE_MPI', parser.userTestMethods[2]['ifdef']) - - line = '@mpitest(npes=[3],ifdef=USE_MPI)' - parser.reset() - m = atMpiTest.match(line) - self.assertTrue(m) - atMpiTest.action(m,line) - is_test_method.apply(nextLine) - self.assertEqual([3], parser.userTestMethods[3]['npRequests']) - self.assertEqual('USE_MPI', parser.userTestMethods[3]['ifdef']) - - - - def testMatchAtTestCase(self): - """Check that a line starting with '@testcase' is detected as an - annotation.""" - nextLine = 'type, extends(TestCase) :: myTestCase\n' - parser = MockParser([nextLine]) - atTestCase = AtTestCase(parser) - - self.assertTrue(atTestCase.match('@testcase')) - self.assertTrue(atTestCase.match(' @testcase')) # leading space - self.assertTrue(atTestCase.match('@TESTCASE')) # case insensitive - self.assertTrue(atTestCase.match('@TestCase')) # mixed case - self.assertFalse(atTestCase.match('@TestCaseb')) # can't have trailing characters without whitespace - - atTestCase.apply('@testCase\n') - self.assertEqual('myTestCase', parser.userTestCase['type']) - self.assertEqual('!@testCase\n', parser.outLines[0]) - self.assertEqual(nextLine, parser.outLines[1]) - - def testMatchAtAssertEqual(self): - """Check that a line starting with '@assertEqual' is detected - as an annotation.""" - parser = MockParser([' \n']) - atAssert = AtAssert(parser) - - self.assertFalse(atAssert.match('@assertEqual')) - self.assertFalse(atAssert.match('@assertEqual()')) - self.assertTrue(atAssert.match('@assertEqual(a, b)')) - self.assertTrue(atAssert.match('@assertequal(a, b)')) # case insensitive - self.assertTrue(atAssert.match('@ASSERTEQUAL(a, b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssert.apply(' @assertEqual(1, 2)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertEqual(1, 2, &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testParseArgsFirstRest(self): - """Test that the first-rest argument parsing is adequate.""" - self.assertEqual(['a1','b1'],parseArgsFirstRest('','a1,b1')) - self.assertEqual(['a4()','b4'],parseArgsFirstRest('','a4(),b4')) - self.assertEqual(['a4%z()','b4'],parseArgsFirstRest('','a4%z(),b4')) - self.assertEqual(['a4','b4%z()'],parseArgsFirstRest('','a4,b4%z()')) - self.assertEqual(['a10','b10,c10'],parseArgsFirstRest('','a10,b10,c10')) - self.assertEqual(['a2'],parseArgsFirstRest("@assertassociated","@assertassociated(a2)")) - self.assertEqual(['a3',"b3,message='This is the message.'"], \ - parseArgsFirstRest("@assertassociated","@assertassociated(a3,b3,message='This is the message.')")) - self.assertEqual(['a','b,c,d'],parseArgsFirstRest("@assertassociated","@assertassociated(a,b,c,d)")) - - def testParseArgsFirstSecondRest(self): - """Test that the first-second-rest argument parsing is adequate.""" - self.assertEqual(None,parseArgsFirstSecondRest("@assertassociated","@assertassociated")) - self.assertEqual(None,parseArgsFirstSecondRest("@assertassociated","@assertassociated()")) - self.assertEqual(['a'],parseArgsFirstSecondRest("@assertassociated","@assertassociated(a)")) - self.assertEqual(['a','b'],parseArgsFirstSecondRest("@assertassociated","@assertassociated(a,b)")) - self.assertEqual(['a','b',"message='This is the message.'"], \ - parseArgsFirstSecondRest("@assertassociated", \ - "@assertassociated(a,b,message='This is the message.')")) - self.assertEqual(['a','b%z()','c,d'],parseArgsFirstSecondRest("@assertassociated", \ - "@assertassociated(a,b%z(),c,d)")) - self.assertEqual(['a4','b4','c4'],parseArgsFirstSecondRest('','a4,b4,c4')) - - - def testMatchAtAssertAssociated(self): - """Check that a line starting with '@assertAssociated' is detected - as an annotation.""" - parser = MockParser([' \n']) - atAssertAssociated = AtAssertAssociated(parser) - - self.assertFalse(atAssertAssociated.match('@assertAssociated')) - self.assertFalse(atAssertAssociated.match('@assertAssociated()')) - self.assertTrue(atAssertAssociated.match('@assertAssociated(a)')) - self.assertTrue(atAssertAssociated.match('@assertassociated(a)')) # case insensitive - self.assertTrue(atAssertAssociated.match('@ASSERTASSOCIATED(a)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertAssociated.apply(' @assertAssociated(a)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertTrue(associated(a), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertAssociatedOverloaded1(self): - """Check that a line starting with '@assertAssociated' is detected - as an annotation. atAssertAssociated(a,b) implies a points to b. - Overriding the name @assertAssociated. - """ - parser = MockParser([' \n']) - atAssertAssociated = AtAssertAssociated(parser) - - self.assertFalse(atAssertAssociated.match('@assertAssociated')) - self.assertFalse(atAssertAssociated.match('@assertAssociated()')) - self.assertTrue(atAssertAssociated.match('@assertAssociated(a)')) - self.assertTrue(atAssertAssociated.match('@assertassociated(a,b)')) # case insensitive - self.assertTrue(atAssertAssociated.match('@ASSERTASSOCIATED(a,b)')) # case insensitive - self.assertTrue(atAssertAssociated.match('@ASSERTASSOCIATED(a_%z(),b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertAssociated.apply(' @assertAssociated(a,b)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertTrue(associated(a,b), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertAssociatedOverloaded2(self): - """Check that a line starting with '@assertAssociated' is detected - as an annotation. atAssertAssociated(a,b) implies a points to b. - Overriding the name @assertAssociated. - """ - parser = MockParser([' \n']) - atAssertAssociated = AtAssertAssociated(parser) - - self.assertFalse(atAssertAssociated.match('@assertAssociated')) - self.assertFalse(atAssertAssociated.match('@assertAssociated()')) - self.assertTrue(atAssertAssociated.match('@assertAssociated(a)')) - self.assertTrue(atAssertAssociated.match('@assertassociated(a,b)')) # case insensitive - self.assertTrue(atAssertAssociated.match('@ASSERTASSOCIATED(a,b)')) # case insensitive - self.assertTrue(atAssertAssociated.match('@ASSERTASSOCIATED(a_%z(),b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertAssociated.apply(' @assertAssociated(a,b,message="c")\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(' call assertTrue(associated(a,b), message="c", &\n', parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - - def testMatchAtAssertUnAssociated(self): - """Check that a line starting with '@assertUnAssociated' is detected - as an annotation.""" - parser = MockParser([' \n']) - atAssertUnAssociated = AtAssertNotAssociated(parser) - - self.assertFalse(atAssertUnAssociated.match('@assertUnAssociated')) - self.assertFalse(atAssertUnAssociated.match('@assertUnAssociated()')) - self.assertTrue(atAssertUnAssociated.match('@assertUnAssociated(a)')) - self.assertTrue(atAssertUnAssociated.match('@assertunassociated(a)')) # case insensitive - self.assertTrue(atAssertUnAssociated.match('@ASSERTUNASSOCIATED(a)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertUnAssociated.apply(' @assertUnAssociated(a)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertFalse(associated(a), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertUnAssociatedWith(self): - """Check that a line starting with '@assertUnAssociatedWith' is detected - as an annotation. atAssertUnAssociated(a,b) implies a points to b.""" - parser = MockParser([' \n']) - atAssertUnAssociated = AtAssertNotAssociated(parser) - - self.assertFalse(atAssertUnAssociated.match('@assertUnAssociated')) - self.assertFalse(atAssertUnAssociated.match('@assertUnAssociated()')) - self.assertTrue(atAssertUnAssociated.match('@assertUnAssociated(a)')) - self.assertTrue(atAssertUnAssociated.match('@assertunassociated(a,b)')) # case insensitive - self.assertTrue(atAssertUnAssociated.match('@ASSERTUNASSOCIATED(a,b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertUnAssociated.apply(' @assertUnAssociated(a,b)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertFalse(associated(a,b), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertNotassociated(self): - """Check that a line starting with '@assertNotAssociated' is detected - as an annotation.""" - parser = MockParser([' \n']) - atAssertNotassociated = AtAssertNotAssociated(parser) - - self.assertFalse(atAssertNotassociated.match('@assertNotassociated')) - self.assertFalse(atAssertNotassociated.match('@assertNotassociated()')) - self.assertTrue(atAssertNotassociated.match('@assertNotassociated(a)')) - self.assertTrue(atAssertNotassociated.match('@assertnotassociated(a)')) # case insensitive - self.assertTrue(atAssertNotassociated.match('@ASSERTNOTASSOCIATED(a)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertNotassociated.apply(' @assertNotassociated(a)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertFalse(associated(a), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertNotassociatedWith(self): - """Check that a line starting with '@assertNotassociatedWith' is detected - as an annotation. atAssertNotassociated(a,b) implies a points to b.""" - parser = MockParser([' \n']) - atAssertNotassociated = AtAssertNotAssociated(parser) - - self.assertFalse(atAssertNotassociated.match('@assertNotassociated')) - self.assertFalse(atAssertNotassociated.match('@assertNotassociated()')) - self.assertTrue(atAssertNotassociated.match('@assertNotassociated(a)')) - self.assertTrue(atAssertNotassociated.match('@assertnotassociated(a,b)')) # case insensitive - self.assertTrue(atAssertNotassociated.match('@ASSERTNOTASSOCIATED(a,b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertNotassociated.apply(' @assertNotassociated(a,b)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertFalse(associated(a,b), &\n", parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - - def testMatchAtAssertEqualUserDefined(self): - """Check that a line starting with '@assertEqualUserDefined' is detected - as an annotation. atAssertEqualUserDefined(a,b) implies a points to b.""" - parser = MockParser([' \n']) - atAssertEqualUserDefined = AtAssertEqualUserDefined(parser) - - self.assertFalse(atAssertEqualUserDefined.match('@assertEqualUserDefined')) - self.assertFalse(atAssertEqualUserDefined.match('@assertEqualUserDefined()')) - self.assertFalse(atAssertEqualUserDefined.match('@assertEqualUserDefined(a)')) - self.assertTrue(atAssertEqualUserDefined.match('@assertequaluserdefined(a,b)')) # case insensitive - self.assertTrue(atAssertEqualUserDefined.match('@ASSERTEQUALUSERDEFINED(a,b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertEqualUserDefined.apply(' @assertEqualUserDefined(a,b)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertTrue(a==b, &\n", parser.outLines[1]) - self.assertEqual(" & message=' not equal to ', &\n", parser.outLines[2]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[3]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[4]) - self.assertEqual(" & 8)", parser.outLines[5]) - self.assertEqual(" )\n", parser.outLines[6]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[7]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[8]) - - def testMatchAtAssertEqualUserDefinedWithMessage(self): - """Check that a line starting with '@assertEqualUserDefined' is detected - as an annotation. atAssertEqualUserDefined(a,b) implies a points to b.""" - parser = MockParser([' \n']) - atAssertEqualUserDefined = AtAssertEqualUserDefined(parser) - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertEqualUserDefined.apply(' @assertEqualUserDefined(a,b,message="c")\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(' call assertTrue(a==b, message="c", &\n', parser.outLines[1]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertEqual(" & 8)", parser.outLines[4]) - self.assertEqual(" )\n", parser.outLines[5]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) - - def testMatchAtAssertEquivalent(self): - """Check that a line starting with '@assertEquivalent' is detected - as an annotation. atAssertEquivalent(a,b) implies a points to b.""" - parser = MockParser([' \n']) - atAssertEquivalent = AtAssertEquivalent(parser) - - self.assertFalse(atAssertEquivalent.match('@assertEquivalent')) - self.assertFalse(atAssertEquivalent.match('@assertEquivalent()')) - self.assertFalse(atAssertEquivalent.match('@assertEquivalent(a)')) - self.assertTrue(atAssertEquivalent.match('@assertequivalent(a,b)')) # case insensitive - self.assertTrue(atAssertEquivalent.match('@ASSERTEQUIVALENT(a,b)')) # case insensitive - - parser.fileName = "foo.pfunit" - parser.currentLineNumber = 8 - atAssertEquivalent.apply(' @assertEquivalent(a,b)\n') - self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) - self.assertEqual(" call assertTrue(a.eqv.b, &\n", parser.outLines[1]) - self.assertEqual(" & message=' not equal to ', &\n", parser.outLines[2]) - self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[3]) - self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[4]) - self.assertEqual(" & 8)", parser.outLines[5]) - self.assertEqual(" )\n", parser.outLines[6]) - self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[7]) - self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[8]) - - - def testMatchAtAssertOther(self): - """Check that a line starting with '@assert*' is detected - as an annotation.""" - parser = MockParser([' \n']) - atAssert = AtAssert(parser) - - self.assertFalse(atAssert.match('@assertTrue')) - self.assertFalse(atAssert.match('@assertTrue()')) - self.assertTrue(atAssert.match('@assertTrue(a)')) - self.assertTrue(atAssert.match('@asserttrue(a)')) # case insensitive - self.assertTrue(atAssert.match('@ASSERTTRUE(a)')) # case insensitive - - parser.fileName = 'foo.pfunit' - parser.currentLineNumber = 8 - atAssert.apply(' @assertTrue(.true.)\n') - self.assertTrue("#line 8 'foo.pfunit'\n", parser.outLines[0]) - self.assertTrue(" call assertTrue(1, 2, &\n", parser.outLines[1]) - self.assertTrue(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertTrue(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertTrue(" & 8)", parser.outLines[4]) - self.assertTrue(" )\n", parser.outLines[5]) - self.assertTrue(" if (anyExceptions()) return\n", parser.outLines[6]) - self.assertTrue("#line 9 'foo.pfunit'\n", parser.outLines[7]) - - def testMatchAtMpiAssert(self): - """Check that a line starting with '@mpiAssert*' is detected - as an annotation.""" - parser = MockParser(['subroutine foo(this)\n']) - atMpiAssert = AtMpiAssert(parser) - - self.assertFalse(atMpiAssert.match('@mpiAssertTrue')) - self.assertFalse(atMpiAssert.match('@mpiAssertTrue()')) - self.assertTrue(atMpiAssert.match('@mpiAssertTrue(a)')) - self.assertTrue(atMpiAssert.match('@mpiAssertTrue(a,b)')) - self.assertTrue(atMpiAssert.match('@mpiasserttrue(a)')) # case insensitive - self.assertTrue(atMpiAssert.match('@MPIASSERTTRUE(a)')) # case insensitive - - parser.fileName = 'foo.pfunit' - parser.currentLineNumber = 8 - atMpiAssert.apply(' @mpiAssertTrue(.true.)\n') - self.assertTrue("#line 8 'foo.pfunit'\n", parser.outLines[0]) - self.assertTrue(" call assertTrue(1, 2, &\n", parser.outLines[1]) - self.assertTrue(" & location=SourceLocation( &\n", parser.outLines[2]) - self.assertTrue(" & 'foo.pfunit', &\n", parser.outLines[3]) - self.assertTrue(" & 8)", parser.outLines[4]) - self.assertTrue(" )\n", parser.outLines[5]) - self.assertTrue(" if (anyExceptions(this%getMpiCommunicator())) return\n", parser.outLines[6]) - self.assertTrue("#line 9 'foo.pfunit'\n", parser.outLines[7]) - - def testMatchAtBefore(self): - """Check that a line starting with '@before*' ...""" - procedure = 'mySetUp' - nextLine = 'subroutine ' + procedure +'()\n' - parser = MockParser([nextLine]) - atBefore = AtBefore(parser) - self.assertTrue(atBefore.match(' @before')) - self.assertFalse(atBefore.match(' @beforeb')) - - atBefore.apply('@before\n') - self.assertEqual(procedure, parser.userTestCase['setUp']) - self.assertEqual('!@before\n', parser.outLines[0]) - self.assertEqual(nextLine, parser.outLines[1]) - - - def testMatchAtAfter(self): - """Check that a line starting with '@after*' ...""" - procedure = 'myTearDown' - nextLine = 'subroutine ' + procedure +'()\n' - parser = MockParser([nextLine]) - atAfter = AtAfter(parser) - self.assertTrue(atAfter.match(' @after')) - self.assertFalse(atAfter.match(' @afterb')) - - atAfter.apply('@after\n') - self.assertEqual(procedure, parser.userTestCase['tearDown']) - self.assertEqual('!@after\n', parser.outLines[0]) - self.assertEqual(nextLine, parser.outLines[1]) - - def testMatchAtSuite(self): - """Check that a line starting with '@suite changes the suite name ...""" - parser = MockParser(['\n']) - atSuite = AtSuite(parser) - self.assertTrue(atSuite.match(" @suite (name='a')")) - self.assertTrue(atSuite.match(" @suite (name=""a"")")) - self.assertTrue(atSuite.match(" @suite(name='aa')")) - self.assertFalse(atSuite.match(" @suite(name=a b)")) - self.assertFalse(atSuite.match(" @suiteb()")) - self.assertFalse(atSuite.match(" @suite()")) - - atSuite.apply("@suite ( name = 'mySuite')\n") - self.assertEqual('mySuite', parser.suiteName) - - -if __name__ == "__main__": - unittest.main() diff --git a/bin/funit/tests/test_parser_assertions.py b/bin/funit/tests/test_parser_assertions.py new file mode 100644 index 00000000..b790aa8c --- /dev/null +++ b/bin/funit/tests/test_parser_assertions.py @@ -0,0 +1,381 @@ +"""Tests for parser assertion directive handling (@assert*, @mpiAssert*).""" + +import unittest +import sys + +sys.path.append("..") +from funit.pFUnitParser import ( + AtAssert, + AtAssertAssociated, + AtAssertNotAssociated, + AtAssertEqualUserDefined, + AtAssertEquivalent, + AtMpiAssert, +) +from funit.tests.parser_test_utils import MockParser + + +class TestParserAssertions(unittest.TestCase): + def testMatchAtAssertEqual(self): + """Check that a line starting with '@assertEqual' is detected + as an annotation.""" + parser = MockParser([" \n"]) + atAssert = AtAssert(parser) + + self.assertFalse(atAssert.match("@assertEqual")) + self.assertFalse(atAssert.match("@assertEqual()")) + self.assertTrue(atAssert.match("@assertEqual(a, b)")) + self.assertTrue(atAssert.match("@assertequal(a, b)")) # case insensitive + self.assertTrue(atAssert.match("@ASSERTEQUAL(a, b)")) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssert.apply(" @assertEqual(1, 2)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertEqual(1, 2, &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertAssociated(self): + """Check that a line starting with '@assertAssociated' is detected + as an annotation.""" + parser = MockParser([" \n"]) + atAssertAssociated = AtAssertAssociated(parser) + + self.assertFalse(atAssertAssociated.match("@assertAssociated")) + self.assertFalse(atAssertAssociated.match("@assertAssociated()")) + self.assertTrue(atAssertAssociated.match("@assertAssociated(a)")) + self.assertTrue( + atAssertAssociated.match("@assertassociated(a)") + ) # case insensitive + self.assertTrue( + atAssertAssociated.match("@ASSERTASSOCIATED(a)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertAssociated.apply(" @assertAssociated(a)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertTrue(associated(a), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertAssociatedOverloaded1(self): + """Check that a line starting with '@assertAssociated' is detected + as an annotation. atAssertAssociated(a,b) implies a points to b. + Overriding the name @assertAssociated. + """ + parser = MockParser([" \n"]) + atAssertAssociated = AtAssertAssociated(parser) + + self.assertFalse(atAssertAssociated.match("@assertAssociated")) + self.assertFalse(atAssertAssociated.match("@assertAssociated()")) + self.assertTrue(atAssertAssociated.match("@assertAssociated(a)")) + self.assertTrue( + atAssertAssociated.match("@assertassociated(a,b)") + ) # case insensitive + self.assertTrue( + atAssertAssociated.match("@ASSERTASSOCIATED(a,b)") + ) # case insensitive + self.assertTrue( + atAssertAssociated.match("@ASSERTASSOCIATED(a_%z(),b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertAssociated.apply(" @assertAssociated(a,b)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertTrue(associated(a,b), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertAssociatedOverloaded2(self): + """Check that a line starting with '@assertAssociated' is detected + as an annotation. atAssertAssociated(a,b) implies a points to b. + Overriding the name @assertAssociated. + """ + parser = MockParser([" \n"]) + atAssertAssociated = AtAssertAssociated(parser) + + self.assertFalse(atAssertAssociated.match("@assertAssociated")) + self.assertFalse(atAssertAssociated.match("@assertAssociated()")) + self.assertTrue(atAssertAssociated.match("@assertAssociated(a)")) + self.assertTrue( + atAssertAssociated.match("@assertassociated(a,b)") + ) # case insensitive + self.assertTrue( + atAssertAssociated.match("@ASSERTASSOCIATED(a,b)") + ) # case insensitive + self.assertTrue( + atAssertAssociated.match("@ASSERTASSOCIATED(a_%z(),b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertAssociated.apply(' @assertAssociated(a,b,message="c")\n') + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual( + ' call assertTrue(associated(a,b), message="c", &\n', parser.outLines[1] + ) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertUnAssociated(self): + """Check that a line starting with '@assertUnAssociated' is detected + as an annotation.""" + parser = MockParser([" \n"]) + atAssertUnAssociated = AtAssertNotAssociated(parser) + + self.assertFalse(atAssertUnAssociated.match("@assertUnAssociated")) + self.assertFalse(atAssertUnAssociated.match("@assertUnAssociated()")) + self.assertTrue(atAssertUnAssociated.match("@assertUnAssociated(a)")) + self.assertTrue( + atAssertUnAssociated.match("@assertunassociated(a)") + ) # case insensitive + self.assertTrue( + atAssertUnAssociated.match("@ASSERTUNASSOCIATED(a)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertUnAssociated.apply(" @assertUnAssociated(a)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertFalse(associated(a), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertUnAssociatedWith(self): + """Check that a line starting with '@assertUnAssociatedWith' is detected + as an annotation. atAssertUnAssociated(a,b) implies a points to b.""" + parser = MockParser([" \n"]) + atAssertUnAssociated = AtAssertNotAssociated(parser) + + self.assertFalse(atAssertUnAssociated.match("@assertUnAssociated")) + self.assertFalse(atAssertUnAssociated.match("@assertUnAssociated()")) + self.assertTrue(atAssertUnAssociated.match("@assertUnAssociated(a)")) + self.assertTrue( + atAssertUnAssociated.match("@assertunassociated(a,b)") + ) # case insensitive + self.assertTrue( + atAssertUnAssociated.match("@ASSERTUNASSOCIATED(a,b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertUnAssociated.apply(" @assertUnAssociated(a,b)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertFalse(associated(a,b), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertNotassociated(self): + """Check that a line starting with '@assertNotAssociated' is detected + as an annotation.""" + parser = MockParser([" \n"]) + atAssertNotassociated = AtAssertNotAssociated(parser) + + self.assertFalse(atAssertNotassociated.match("@assertNotassociated")) + self.assertFalse(atAssertNotassociated.match("@assertNotassociated()")) + self.assertTrue(atAssertNotassociated.match("@assertNotassociated(a)")) + self.assertTrue( + atAssertNotassociated.match("@assertnotassociated(a)") + ) # case insensitive + self.assertTrue( + atAssertNotassociated.match("@ASSERTNOTASSOCIATED(a)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertNotassociated.apply(" @assertNotassociated(a)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertFalse(associated(a), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertNotassociatedWith(self): + """Check that a line starting with '@assertNotassociatedWith' is detected + as an annotation. atAssertNotassociated(a,b) implies a points to b.""" + parser = MockParser([" \n"]) + atAssertNotassociated = AtAssertNotAssociated(parser) + + self.assertFalse(atAssertNotassociated.match("@assertNotassociated")) + self.assertFalse(atAssertNotassociated.match("@assertNotassociated()")) + self.assertTrue(atAssertNotassociated.match("@assertNotassociated(a)")) + self.assertTrue( + atAssertNotassociated.match("@assertnotassociated(a,b)") + ) # case insensitive + self.assertTrue( + atAssertNotassociated.match("@ASSERTNOTASSOCIATED(a,b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertNotassociated.apply(" @assertNotassociated(a,b)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertFalse(associated(a,b), &\n", parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertEqualUserDefined(self): + """Check that a line starting with '@assertEqualUserDefined' is detected + as an annotation. atAssertEqualUserDefined(a,b) implies a points to b.""" + parser = MockParser([" \n"]) + atAssertEqualUserDefined = AtAssertEqualUserDefined(parser) + + self.assertFalse(atAssertEqualUserDefined.match("@assertEqualUserDefined")) + self.assertFalse(atAssertEqualUserDefined.match("@assertEqualUserDefined()")) + self.assertFalse(atAssertEqualUserDefined.match("@assertEqualUserDefined(a)")) + self.assertTrue( + atAssertEqualUserDefined.match("@assertequaluserdefined(a,b)") + ) # case insensitive + self.assertTrue( + atAssertEqualUserDefined.match("@ASSERTEQUALUSERDEFINED(a,b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertEqualUserDefined.apply(" @assertEqualUserDefined(a,b)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertTrue(a==b, &\n", parser.outLines[1]) + self.assertEqual(" & message=' not equal to ', &\n", parser.outLines[2]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[3]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[4]) + self.assertEqual(" & 8)", parser.outLines[5]) + self.assertEqual(" )\n", parser.outLines[6]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[7]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[8]) + + def testMatchAtAssertEqualUserDefinedWithMessage(self): + """Check that a line starting with '@assertEqualUserDefined' is detected + as an annotation. atAssertEqualUserDefined(a,b) implies a points to b.""" + parser = MockParser([" \n"]) + atAssertEqualUserDefined = AtAssertEqualUserDefined(parser) + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertEqualUserDefined.apply(' @assertEqualUserDefined(a,b,message="c")\n') + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(' call assertTrue(a==b, message="c", &\n', parser.outLines[1]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertEqual(" & 8)", parser.outLines[4]) + self.assertEqual(" )\n", parser.outLines[5]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[7]) + + def testMatchAtAssertEquivalent(self): + """Check that a line starting with '@assertEquivalent' is detected + as an annotation. atAssertEquivalent(a,b) implies a points to b.""" + parser = MockParser([" \n"]) + atAssertEquivalent = AtAssertEquivalent(parser) + + self.assertFalse(atAssertEquivalent.match("@assertEquivalent")) + self.assertFalse(atAssertEquivalent.match("@assertEquivalent()")) + self.assertFalse(atAssertEquivalent.match("@assertEquivalent(a)")) + self.assertTrue( + atAssertEquivalent.match("@assertequivalent(a,b)") + ) # case insensitive + self.assertTrue( + atAssertEquivalent.match("@ASSERTEQUIVALENT(a,b)") + ) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssertEquivalent.apply(" @assertEquivalent(a,b)\n") + self.assertEqual('#line 8 "foo.pfunit"\n', parser.outLines[0]) + self.assertEqual(" call assertTrue(a.eqv.b, &\n", parser.outLines[1]) + self.assertEqual(" & message=' not equal to ', &\n", parser.outLines[2]) + self.assertEqual(" & location=SourceLocation( &\n", parser.outLines[3]) + self.assertEqual(" & 'foo.pfunit', &\n", parser.outLines[4]) + self.assertEqual(" & 8)", parser.outLines[5]) + self.assertEqual(" )\n", parser.outLines[6]) + self.assertEqual(" if (anyExceptions()) return\n", parser.outLines[7]) + self.assertEqual('#line 9 "foo.pfunit"\n', parser.outLines[8]) + + def testMatchAtAssertOther(self): + """Check that a line starting with '@assert*' is detected + as an annotation.""" + parser = MockParser([" \n"]) + atAssert = AtAssert(parser) + + self.assertFalse(atAssert.match("@assertTrue")) + self.assertFalse(atAssert.match("@assertTrue()")) + self.assertTrue(atAssert.match("@assertTrue(a)")) + self.assertTrue(atAssert.match("@asserttrue(a)")) # case insensitive + self.assertTrue(atAssert.match("@ASSERTTRUE(a)")) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atAssert.apply(" @assertTrue(.true.)\n") + self.assertTrue("#line 8 'foo.pfunit'\n", parser.outLines[0]) + self.assertTrue(" call assertTrue(1, 2, &\n", parser.outLines[1]) + self.assertTrue(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertTrue(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertTrue(" & 8)", parser.outLines[4]) + self.assertTrue(" )\n", parser.outLines[5]) + self.assertTrue(" if (anyExceptions()) return\n", parser.outLines[6]) + self.assertTrue("#line 9 'foo.pfunit'\n", parser.outLines[7]) + + def testMatchAtMpiAssert(self): + """Check that a line starting with '@mpiAssert*' is detected + as an annotation.""" + parser = MockParser(["subroutine foo(this)\n"]) + atMpiAssert = AtMpiAssert(parser) + + self.assertFalse(atMpiAssert.match("@mpiAssertTrue")) + self.assertFalse(atMpiAssert.match("@mpiAssertTrue()")) + self.assertTrue(atMpiAssert.match("@mpiAssertTrue(a)")) + self.assertTrue(atMpiAssert.match("@mpiAssertTrue(a,b)")) + self.assertTrue(atMpiAssert.match("@mpiasserttrue(a)")) # case insensitive + self.assertTrue(atMpiAssert.match("@MPIASSERTTRUE(a)")) # case insensitive + + parser.fileName = "foo.pfunit" + parser.currentLineNumber = 8 + atMpiAssert.apply(" @mpiAssertTrue(.true.)\n") + self.assertTrue("#line 8 'foo.pfunit'\n", parser.outLines[0]) + self.assertTrue(" call assertTrue(1, 2, &\n", parser.outLines[1]) + self.assertTrue(" & location=SourceLocation( &\n", parser.outLines[2]) + self.assertTrue(" & 'foo.pfunit', &\n", parser.outLines[3]) + self.assertTrue(" & 8)", parser.outLines[4]) + self.assertTrue(" )\n", parser.outLines[5]) + self.assertTrue( + " if (anyExceptions(this%getMpiCommunicator())) return\n", + parser.outLines[6], + ) + self.assertTrue("#line 9 'foo.pfunit'\n", parser.outLines[7]) + + +if __name__ == "__main__": + unittest.main() diff --git a/bin/funit/tests/test_parser_continuation.py b/bin/funit/tests/test_parser_continuation.py new file mode 100644 index 00000000..3d16b9e9 --- /dev/null +++ b/bin/funit/tests/test_parser_continuation.py @@ -0,0 +1,116 @@ +"""Tests for Fortran line continuation handling in parser.""" + +import io +import unittest +import sys + +sys.path.append("..") +from funit.pFUnitParser import Parser +from funit.tests.parser_test_utils import MockWriter + + +class TestParserContinuation(unittest.TestCase): + def test_fortran_continuation_in_macro(self): + """ + Check that macro line continued with '&' is joined properly. + """ + # Simulated input lines as if read from a Fortran source file + logical_macro = '@test(timeout = 3.0, foo="long string" &\n' + logical_macro2 = " & , bar=99)\n" + input_lines = [logical_macro, logical_macro2] + p = Parser.__new__(Parser) # bypass __init__ + test_input = "".join(input_lines) + p.inputFile = io.StringIO(test_input) + p.currentLineNumber = 0 + p.outputFile = MockWriter(p) + # Patch isComment to always return False for every input line here + p.isComment = lambda line: False + + full_line = p.nextLine() + expected = '@test(timeout = 3.0, foo="long string" , bar=99)\n' + self.assertEqual(full_line, expected) + + def test_assert_equal_multiline(self): + """ + Check that an @assertEqual statement split over multiple lines + (via Fortran continuation & syntax) is joined correctly. + """ + input_lines = [ + "@assertEqual(lhs_value, &\n", + " &rhs_function(arg1, arg2), &\n", + ' &"Comparison failed message")\n', + ] + combined = "".join(input_lines) + p = Parser.__new__(Parser) + p.inputFile = io.StringIO(combined) + p.currentLineNumber = 0 + p.outputFile = MockWriter(p) + p.isComment = lambda line: False + full_line = p.nextLine() + expected = '@assertEqual(lhs_value, rhs_function(arg1, arg2), "Comparison failed message")\n' + self.assertEqual(full_line, expected) + + def test_lines_without_continuation_not_collapsed(self): + """ + Check that separate lines without '&' continuation are returned + one at a time, not collapsed together. + """ + input_lines = [ + "module Test_Foo\n", + " use funit\n", + " implicit none\n", + "contains\n", + ] + combined = "".join(input_lines) + p = Parser.__new__(Parser) + p.inputFile = io.StringIO(combined) + p.currentLineNumber = 0 + p.outputFile = MockWriter(p) + p.isComment = lambda line: False + + # Each call to nextLine() should return exactly one line + line1 = p.nextLine() + self.assertEqual(line1, "module Test_Foo\n") + + line2 = p.nextLine() + self.assertEqual(line2, " use funit\n") + + line3 = p.nextLine() + self.assertEqual(line3, " implicit none\n") + + line4 = p.nextLine() + self.assertEqual(line4, "contains\n") + + def test_test_directive_not_collapsed_with_next_line(self): + """ + Check that @test directive is NOT collapsed with the following + subroutine line when there's no '&' continuation. + """ + input_lines = [ + "@test\n", + "subroutine test_foo()\n", + " call do_something()\n", + "end subroutine test_foo\n", + ] + combined = "".join(input_lines) + p = Parser.__new__(Parser) + p.inputFile = io.StringIO(combined) + p.currentLineNumber = 0 + p.outputFile = MockWriter(p) + p.isComment = lambda line: False + + line1 = p.nextLine() + self.assertEqual(line1, "@test\n") + + line2 = p.nextLine() + self.assertEqual(line2, "subroutine test_foo()\n") + + line3 = p.nextLine() + self.assertEqual(line3, " call do_something()\n") + + line4 = p.nextLine() + self.assertEqual(line4, "end subroutine test_foo\n") + + +if __name__ == "__main__": + unittest.main() diff --git a/bin/funit/tests/test_parser_directives.py b/bin/funit/tests/test_parser_directives.py new file mode 100644 index 00000000..18083dba --- /dev/null +++ b/bin/funit/tests/test_parser_directives.py @@ -0,0 +1,209 @@ +"""Tests for parser directive handling (@test, @before, @after, @suite, @testcase, @mpitest).""" + +import unittest +import sys + +sys.path.append("..") +from funit.pFUnitParser import ( + AtTest, + AtMpiTest, + AtTestCase, + AtBefore, + AtAfter, + AtSuite, + IsTestMethod, + MyError, +) +from funit.tests.parser_test_utils import MockParser + + +class TestParserDirectives(unittest.TestCase): + def testAtTest(self): + """Check that a line starting with '@test' is detected as an + annotation.""" + nextLine = "subroutine myTest()\n" + parser = MockParser([nextLine]) + atTest = AtTest(parser) + is_test_method = IsTestMethod(parser) + + self.assertTrue(atTest.match("@test")) + self.assertFalse(atTest.match("! @test")) + self.assertTrue(atTest.match(" @test")) # leading space + self.assertTrue(atTest.match("@TEST")) # case insensitive + self.assertTrue(atTest.match("@Test")) # mixed case + self.assertFalse( + atTest.match("@Testb") + ) # can't have trailing characters without whitespace + self.assertTrue(atTest.match("@Test (b)")) + self.assertTrue(atTest.match("@Test(b)")) + self.assertFalse( + atTest.match("@Test b") + ) # next non-space character needs to be '(') + self.assertTrue(atTest.match("@Test(timeout=3.0)")) + self.assertFalse(atTest.match("! @Test")) + self.assertFalse(atTest.match("@assertTrue")) + self.assertTrue(atTest.match("@test(cases = [1,3,5])")) + + atTest.apply("@test\n") + is_test_method.apply(nextLine) + self.assertEqual("myTest", parser.userTestMethods[0]["name"]) + self.assertEqual("!@test\n", parser.outLines[0]) + self.assertEqual(nextLine, parser.outLines[1]) + + def testAtTestNoParens(self): + """Check that test procedure with no parens is accepted.""" + nextLine = "subroutine myTest ! and a comment \n" + parser = MockParser([nextLine]) + atTest = AtTest(parser) + is_test_method = IsTestMethod(parser) + + m = atTest.match("@test\n") + atTest.action(m, "@test\n") + is_test_method.apply(nextLine) + self.assertEqual("myTest", parser.userTestMethods[0]["name"]) + self.assertEqual("!@test\n", parser.outLines[0]) + self.assertEqual(nextLine, parser.outLines[1]) + + @unittest.skip("I don't think this test can pass with the new multi-stage parsing") + def testAtTestFail(self): + """Check that useful error is sent if next line is not properly formatted.""" + + nextLine = "subroutine myTest (] \n" # bad closing paren + parser = MockParser([nextLine]) + + with self.assertRaises(MyError): + atTest = AtTest(parser) + line = "@test" + m = atTest.match(line) + atTest.action(m, line) + + is_test_method = IsTestMethod(parser) + is_test_method.apply(nextLine) + + def testAtTestSkipComment(self): + """Ignore comment lines between @test and subroutine foo().""" + nextLineA = "! ignore this line \n" + nextLineB = "\n" + nextLineC = "subroutine myTestC()\n" + parser = MockParser([nextLineA, nextLineB, nextLineC]) + + atTest = AtTest(parser) + atTest.apply("@test\n") + is_test_method = IsTestMethod(parser) + is_test_method.apply(nextLineA) + is_test_method.apply(nextLineB) + is_test_method.apply(nextLineC) + self.assertEqual("myTestC", parser.userTestMethods[0]["name"]) + self.assertEqual("!@test\n", parser.outLines[0]) + self.assertEqual(nextLineC, parser.outLines[1]) + + def testAtMpiTest(self): + """Check that a line starting with '@mpitest' is detected as an + annotation and that optional parameters are collected.""" + + nextLine = "subroutine myTest(this)\n" + parser = MockParser([nextLine]) + atMpiTest = AtMpiTest(parser) + is_test_method = IsTestMethod(parser) + + line = "@mpitest(npes=[1])" + m = atMpiTest.match(line) + self.assertTrue(m) + atMpiTest.action(m, line) + is_test_method.apply(nextLine) + self.assertEqual([1], parser.userTestMethods[0]["npRequests"]) + self.assertFalse("ifdef" in parser.userTestMethods[0]) + + # ignore leading space? + line = "@mpitest( npes=[1])" + parser.reset() + m = atMpiTest.match(line) + self.assertTrue(m) + atMpiTest.action(m, line) + is_test_method.apply(nextLine) + self.assertEqual([1], parser.userTestMethods[1]["npRequests"]) + self.assertFalse("ifdef" in parser.userTestMethods[1]) + + line = "@mpitest(npes=[1, 2,3], ifdef=USE_MPI)" + parser.reset() + m = atMpiTest.match(line) + self.assertTrue(m) + atMpiTest.action(m, line) + is_test_method.apply(nextLine) + self.assertEqual([1, 2, 3], parser.userTestMethods[2]["npRequests"]) + self.assertEqual("USE_MPI", parser.userTestMethods[2]["ifdef"]) + + line = "@mpitest(npes=[3],ifdef=USE_MPI)" + parser.reset() + m = atMpiTest.match(line) + self.assertTrue(m) + atMpiTest.action(m, line) + is_test_method.apply(nextLine) + self.assertEqual([3], parser.userTestMethods[3]["npRequests"]) + self.assertEqual("USE_MPI", parser.userTestMethods[3]["ifdef"]) + + def testMatchAtTestCase(self): + """Check that a line starting with '@testcase' is detected as an + annotation.""" + nextLine = "type, extends(TestCase) :: myTestCase\n" + parser = MockParser([nextLine]) + atTestCase = AtTestCase(parser) + + self.assertTrue(atTestCase.match("@testcase")) + self.assertTrue(atTestCase.match(" @testcase")) # leading space + self.assertTrue(atTestCase.match("@TESTCASE")) # case insensitive + self.assertTrue(atTestCase.match("@TestCase")) # mixed case + self.assertFalse( + atTestCase.match("@TestCaseb") + ) # can't have trailing characters without whitespace + + atTestCase.apply("@testCase\n") + self.assertEqual("myTestCase", parser.userTestCase["type"]) + self.assertEqual("!@testCase\n", parser.outLines[0]) + self.assertEqual(nextLine, parser.outLines[1]) + + def testMatchAtBefore(self): + """Check that a line starting with '@before*' ...""" + procedure = "mySetUp" + nextLine = "subroutine " + procedure + "()\n" + parser = MockParser([nextLine]) + atBefore = AtBefore(parser) + self.assertTrue(atBefore.match(" @before")) + self.assertFalse(atBefore.match(" @beforeb")) + + atBefore.apply("@before\n") + self.assertEqual(procedure, parser.userTestCase["setUp"]) + self.assertEqual("!@before\n", parser.outLines[0]) + self.assertEqual(nextLine, parser.outLines[1]) + + def testMatchAtAfter(self): + """Check that a line starting with '@after*' ...""" + procedure = "myTearDown" + nextLine = "subroutine " + procedure + "()\n" + parser = MockParser([nextLine]) + atAfter = AtAfter(parser) + self.assertTrue(atAfter.match(" @after")) + self.assertFalse(atAfter.match(" @afterb")) + + atAfter.apply("@after\n") + self.assertEqual(procedure, parser.userTestCase["tearDown"]) + self.assertEqual("!@after\n", parser.outLines[0]) + self.assertEqual(nextLine, parser.outLines[1]) + + def testMatchAtSuite(self): + """Check that a line starting with '@suite changes the suite name ...""" + parser = MockParser(["\n"]) + atSuite = AtSuite(parser) + self.assertTrue(atSuite.match(" @suite (name='a')")) + self.assertTrue(atSuite.match(" @suite (name=a)")) + self.assertTrue(atSuite.match(" @suite(name='aa')")) + self.assertFalse(atSuite.match(" @suite(name=a b)")) + self.assertFalse(atSuite.match(" @suiteb()")) + self.assertFalse(atSuite.match(" @suite()")) + + atSuite.apply("@suite ( name = 'mySuite')\n") + self.assertEqual("mySuite", parser.suiteName) + + +if __name__ == "__main__": + unittest.main() diff --git a/bin/funit/tests/test_parser_utils.py b/bin/funit/tests/test_parser_utils.py new file mode 100644 index 00000000..b90cf3dc --- /dev/null +++ b/bin/funit/tests/test_parser_utils.py @@ -0,0 +1,96 @@ +"""Tests for parser utility functions.""" + +import unittest +import sys + +sys.path.append("..") +from funit.pFUnitParser import ( + cppSetLineAndFile, + getSubroutineName, + getSelfObjectName, + getTypeName, + parseArgsFirstRest, + parseArgsFirstSecondRest, +) + + +class TestParserUtils(unittest.TestCase): + def testCppSetLineAndFile(self): + self.assertEqual('#line 7 "foo"\n', cppSetLineAndFile(7, "foo")) + self.assertEqual('#line 3 "bar"\n', cppSetLineAndFile(3, "bar")) + + def testGetSubroutineName(self): + self.assertEqual("a", getSubroutineName("subroutine a()")) + self.assertEqual("abcd", getSubroutineName("subroutine abcd ()")) + + def testGetSelfObjectName(self): + self.assertEqual("b", getSelfObjectName("subroutine a(b)")) + self.assertEqual("bc", getSelfObjectName("subroutine a(bc)")) + self.assertEqual("bc", getSelfObjectName("subroutine a(bc,d)")) + self.assertEqual("bc", getSelfObjectName("subroutine a(bc, d)")) + self.assertEqual("bc", getSelfObjectName("subroutine a(bc ,d)")) + self.assertEqual("bc", getSelfObjectName("subroutine a(bc , d)")) + + def testGetTypeName(self): + self.assertEqual("foo", getTypeName(" type :: foo")) + self.assertEqual("foo", getTypeName(" type, extends(something) :: foo")) + self.assertEqual("foo", getTypeName(" type, abstract :: foo")) + self.assertEqual( + "foo", getTypeName(" type, extends(something), abstract :: foo") + ) + + def testParseArgsFirstRest(self): + """Test that the first-rest argument parsing is adequate.""" + self.assertEqual(["a1", "b1"], parseArgsFirstRest("", "a1,b1")) + self.assertEqual(["a4()", "b4"], parseArgsFirstRest("", "a4(),b4")) + self.assertEqual(["a4%z()", "b4"], parseArgsFirstRest("", "a4%z(),b4")) + self.assertEqual(["a4", "b4%z()"], parseArgsFirstRest("", "a4,b4%z()")) + self.assertEqual(["a10", "b10,c10"], parseArgsFirstRest("", "a10,b10,c10")) + self.assertEqual( + ["a2"], parseArgsFirstRest("@assertassociated", "@assertassociated(a2)") + ) + self.assertEqual( + ["a3", "b3,message='This is the message.'"], + parseArgsFirstRest( + "@assertassociated", + "@assertassociated(a3,b3,message='This is the message.')", + ), + ) + self.assertEqual( + ["a", "b,c,d"], + parseArgsFirstRest("@assertassociated", "@assertassociated(a,b,c,d)"), + ) + + def testParseArgsFirstSecondRest(self): + """Test that the first-second-rest argument parsing is adequate.""" + self.assertEqual( + None, parseArgsFirstSecondRest("@assertassociated", "@assertassociated") + ) + self.assertEqual( + None, parseArgsFirstSecondRest("@assertassociated", "@assertassociated()") + ) + self.assertEqual( + ["a"], parseArgsFirstSecondRest("@assertassociated", "@assertassociated(a)") + ) + self.assertEqual( + ["a", "b"], + parseArgsFirstSecondRest("@assertassociated", "@assertassociated(a,b)"), + ) + self.assertEqual( + ["a", "b", "message='This is the message.'"], + parseArgsFirstSecondRest( + "@assertassociated", + "@assertassociated(a,b,message='This is the message.')", + ), + ) + self.assertEqual( + ["a", "b%z()", "c,d"], + parseArgsFirstSecondRest( + "@assertassociated", "@assertassociated(a,b%z(),c,d)" + ), + ) + self.assertEqual(["a4", "b4", "c4"], parseArgsFirstSecondRest("", "a4,b4,c4")) + + +if __name__ == "__main__": + unittest.main()