diff --git a/.lib/git-fleximod/git_fleximod/cli.py b/.lib/git-fleximod/git_fleximod/cli.py index e4acc5558..7640bebe2 100644 --- a/.lib/git-fleximod/git_fleximod/cli.py +++ b/.lib/git-fleximod/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse, os, sys from git_fleximod import utils -__version__ = "1.0.3" +__version__ = "1.1.0" class CustomArgumentParser(argparse.ArgumentParser): def print_help(self, file=None): @@ -116,6 +116,13 @@ def get_parser(): "verbosity level each time.", ) + parser.add_argument( + "--no-mods-details", + action="store_true", + default=False, + help="Suppress details on local mods in status output." + ) + parser.add_argument( "-V", "--version", diff --git a/.lib/git-fleximod/git_fleximod/git_fleximod.py b/.lib/git-fleximod/git_fleximod/git_fleximod.py index b3c4fece4..51a68a817 100755 --- a/.lib/git-fleximod/git_fleximod/git_fleximod.py +++ b/.lib/git-fleximod/git_fleximod/git_fleximod.py @@ -69,6 +69,7 @@ def commandline_arguments(args=None): options.components, options.exclude, options.force, + options.no_mods_details, action, ) @@ -189,29 +190,30 @@ def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger): fxrequired = gitmodules.get(name, "fxrequired") return Submodule(root_dir, name, path, url, fxtag=tag, fxurl=fxurl, fxsparse=fxsparse, fxrequired=fxrequired, logger=logger) -def submodules_status(gitmodules, root_dir, toplevel=False, depth=0): +def submodules_status(gitmodules, root_dir, toplevel=False, depth=0, no_mods_details=False): testfails = 0 localmods = 0 needsupdate = 0 - wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20)) for name in gitmodules.sections(): submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) - - result,n,l,t = submod.status() + + result,n,l,t = submod.status(depth=depth, no_mods_details=no_mods_details) if toplevel or not submod.toplevel(): - print(wrapper.fill(result)) + print(result) testfails += t localmods += l needsupdate += n subdir = os.path.join(root_dir, submod.path) if os.path.exists(os.path.join(subdir, ".gitmodules")): gsubmod = GitModules(logger, confpath=subdir) - t,l,n = submodules_status(gsubmod, subdir, depth=depth+1) + t, l, n = submodules_status( + gsubmod, subdir, depth=depth + 1, no_mods_details=no_mods_details + ) if toplevel or not submod.toplevel(): testfails += t localmods += l needsupdate += n - + return testfails, localmods, needsupdate def git_toplevelroot(root_dir, logger): @@ -271,7 +273,7 @@ def local_mods_output(): """ print(text) -def submodules_test(gitmodules, root_dir): +def submodules_test(gitmodules, root_dir, no_mods_details=False): """ This function tests the git submodules based on the provided parameters. @@ -282,12 +284,15 @@ def submodules_test(gitmodules, root_dir): Parameters: gitmodules (ConfigParser): The gitmodules configuration. root_dir (str): The root directory for the git operation. + no_mods_details (bool, optional): If True, suppress details on local mods in status output Returns: int: The number of test failures. """ # First check that fxtags are present and in sync with submodule hashes - testfails, localmods, needsupdate = submodules_status(gitmodules, root_dir) + testfails, localmods, needsupdate = submodules_status( + gitmodules, root_dir, no_mods_details=no_mods_details + ) print("") # Then make sure that urls are consistant with fxurls (not forks and not ssh) # and that sparse checkout files exist @@ -315,6 +320,7 @@ def main(): includelist, excludelist, force, + no_mods_details, action, ) = commandline_arguments() # Get a logger for the package @@ -322,7 +328,7 @@ def main(): logger = logging.getLogger(__name__) logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name)) - + if not root_dir or not os.path.isfile(os.path.join(root_dir, file_name)): if root_dir: file_path = utils.find_upwards(root_dir, file_name) @@ -352,7 +358,9 @@ def main(): if action == "update": asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force)) elif action == "status": - tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True) + tfails, lmods, updates = submodules_status( + gitmodules, root_dir, toplevel=True, no_mods_details=no_mods_details + ) if tfails + lmods + updates > 0: print( f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n" @@ -360,7 +368,7 @@ def main(): if lmods > 0: local_mods_output() elif action == "test": - retval = submodules_test(gitmodules, root_dir) + retval = submodules_test(gitmodules, root_dir, no_mods_details=no_mods_details) else: utils.fatal_error(f"unrecognized action request {action}") return retval diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index c37e13540..6d0e58efb 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -38,13 +38,19 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N self.fxrequired = "AlwaysRequired" self.logger = logger - def status(self): + def status(self, depth=0, no_mods_details=False): """ Checks the status of the submodule and returns 4 parameters: - result (str): The status of the submodule. - needsupdate (bool): An indicator if the submodule needs to be updated. - localmods (bool): An indicator if the submodule has local modifications. - testfails (bool): An indicator if the submodule has failed a test, this is used for testing purposes. + + Args: + depth (int, optional): depth of this submodule relative to root, used to + indent output for nested submodules + no_mods_details (bool, optional): if True, suppress details on local mods in + status output """ smpath = os.path.join(self.root_dir, self.path) @@ -52,6 +58,18 @@ def status(self): localmods = False needsupdate = False ahash = None + + # The following prefix gives a tree-like output: + tree_prefix_spaces = 3 + if depth == 0: + tree_prefix = "" + else: + tree_prefix = " " * tree_prefix_spaces * (depth - 1) + "└─ " + + full_name = tree_prefix + self.name + name_width = 20 + full_width = name_width + len(tree_prefix) + optional = "" if "Optional" in self.fxrequired: optional = " (optional)" @@ -78,7 +96,7 @@ def status(self): if hhash and atag: break if self.fxtag and (ahash == hhash or atag == self.fxtag): - result = f"e {self.name:>20} not checked out, aligned at tag {self.fxtag}{optional}" + result = f"e {full_name:<{full_width}} not checked out, aligned at tag {self.fxtag}{optional}" needsupdate = True elif self.fxtag: status, ahash = rootgit.git_operation( @@ -86,20 +104,20 @@ def status(self): ) ahash = ahash[1 : len(self.fxtag) + 1] if self.fxtag == ahash: - result = f"e {self.name:>20} not checked out, aligned at hash {ahash}{optional}" + result = f"e {full_name:<{full_width}} not checked out, aligned at hash {ahash}{optional}" else: - result = f"e {self.name:>20} not checked out, out of sync at tag {atag}, expected tag is {self.fxtag}{optional}" + result = f"e {full_name:<{full_width}} not checked out, out of sync at tag {atag}, expected tag is {self.fxtag}{optional}" testfails = True needsupdate = True else: - result = f"e {self.name:>20} has no fxtag defined in .gitmodules{optional}" + result = f"e {full_name:<{full_width}} has no fxtag defined in .gitmodules{optional}" testfails = False else: with utils.pushd(smpath): git = GitInterface(smpath, self.logger) status, remote = git.git_operation("remote") if remote == '': - result = f"e {self.name:>20} has no associated remote" + result = f"e {full_name:<{full_width}} has no associated remote" testfails = True needsupdate = True return result, needsupdate, localmods, testfails @@ -123,36 +141,76 @@ def status(self): if rurl != self.url: remote = self._add_remote(git) git.git_operation("fetch", remote) + + mod_char = " " + _, status_output = git.git_operation("status", "--ignore-submodules", "-uno") + if "nothing to commit" not in status_output: + localmods = True + mod_char = "M" + # Asked for a tag and found that tag if self.fxtag and atag == self.fxtag: - result = f" {self.name:>20} at tag {self.fxtag}" + result = f" {mod_char} {full_name:<{full_width}} at tag {self.fxtag}" recurse = True testfails = False # Asked for and found a hash elif self.fxtag and (ahash[: len(self.fxtag)] == self.fxtag or (self.fxtag.find(ahash)==0)): - result = f" {self.name:>20} at hash {ahash}" + result = f" {mod_char} {full_name:<{full_width}} at hash {ahash}" recurse = True testfails = False # Asked for and found a hash elif atag == ahash: - result = f" {self.name:>20} at hash {ahash}" + result = f" {mod_char} {full_name:<{full_width}} at hash {ahash}" recurse = True # Did not find requested tag or hash elif self.fxtag: - result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}" + result = f"s{mod_char} {full_name:<{full_width}} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}" testfails = True needsupdate = True else: if atag: - result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" + result = f"e{mod_char} {full_name:<{full_width}} has no fxtag defined in .gitmodules, module at {atag}" else: - result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {ahash}" + result = f"e{mod_char} {full_name:<{full_width}} has no fxtag defined in .gitmodules, module at {ahash}" testfails = False - status, output = git.git_operation("status", "--ignore-submodules", "-uno") - if "nothing to commit" not in output: - localmods = True - result = "M" + textwrap.indent(output, " ") + if localmods and not no_mods_details: + # Print details about the local mods, indented below the other + # information about this submodule. + # + # We use a vertical bar to help with the visual alignment of child + # submodules. There are two main goals of the spacing details here: + # 1. If there is a child of this submodule, the vertical bar used here + # should connect with the vertical part of the tree_prefix. + # 2. The details about any local mods should be indented an additional + # 4 spaces beyond the start of the text like "at tag ..." + # + # Here are details on how we accomplish these goals: + # - leading_spaces: This is key for accomplishing the first goal. We + # need 3 spaces for the first three characters in the output (two + # status characters and a space), plus an additional number of + # spaces matching the number of spaces that would be used in the + # tree_prefix of any *child* of this submodule. + # - total_indent: This is the total indent needed to achieve the + # second goal. The first addition of 4 aligns the output with the + # status (e.g., "at tag ..."), accounting for the 3 leading + # characters before the name and the 1 trailing space after the + # name. The second addition of 4 indents the details about local + # mods an additional 4 spaces. + # - trailing_spaces: This gives the correct total indentation given + # that we already have some leading spaces plus a vertical bar + # character. + leading_spaces = " " * (3 + depth * tree_prefix_spaces) + total_indent = full_width + 4 + 4 + trailing_spaces = " " * (total_indent - len(leading_spaces) - 1) + result = result + "\n" + textwrap.indent(status_output, + leading_spaces + "│" + trailing_spaces, + # The following predicate + # makes the vertical bar + # appear even for blank + # lines: + predicate = lambda _: True) + # print(f"result {result} needsupdate {needsupdate} localmods {localmods} testfails {testfails}") return result, needsupdate, localmods, testfails diff --git a/.lib/git-fleximod/pyproject.toml b/.lib/git-fleximod/pyproject.toml index 211eac1dd..c5a2bb0d8 100644 --- a/.lib/git-fleximod/pyproject.toml +++ b/.lib/git-fleximod/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "1.0.3" +version = "1.1.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/.lib/git-fleximod/tbump.toml b/.lib/git-fleximod/tbump.toml index 20271f4de..cce9de850 100644 --- a/.lib/git-fleximod/tbump.toml +++ b/.lib/git-fleximod/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "1.0.3" +current = "1.1.0" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/.lib/git-fleximod/tests/test_b_update.py b/.lib/git-fleximod/tests/test_b_update.py index 159f1cfae..570ffa2cb 100644 --- a/.lib/git-fleximod/tests/test_b_update.py +++ b/.lib/git-fleximod/tests/test_b_update.py @@ -1,6 +1,7 @@ import pytest from pathlib import Path - +from tests.utils_for_tests import normalize_whitespace + def test_basic_checkout(git_fleximod, test_repo, shared_repos): # Prepare a simple .gitmodules gm = shared_repos['gitmodules_content'] @@ -9,7 +10,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos): repo_path = shared_repos["subrepo_path"] file_path.write_text(gm) - + # Run the command result = git_fleximod(test_repo, f"update {repo_name}") @@ -19,8 +20,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos): if "sparse" in repo_name: assert Path(test_repo / f"{repo_path}/m4").exists() # Did the submodule sparse directory get created? assert not Path(test_repo / f"{repo_path}/README").exists() # Did only the submodule sparse directory get created? - + status = git_fleximod(test_repo, f"status {repo_name}") - - assert shared_repos["status2"] in status.stdout - + + assert shared_repos["status2"] in normalize_whitespace(status.stdout) diff --git a/.lib/git-fleximod/tests/test_c_required.py b/.lib/git-fleximod/tests/test_c_required.py index 2ac661451..2ba17f1b9 100644 --- a/.lib/git-fleximod/tests/test_c_required.py +++ b/.lib/git-fleximod/tests/test_c_required.py @@ -1,7 +1,8 @@ import pytest import re from pathlib import Path - +from tests.utils_for_tests import normalize_whitespace + def test_required(git_fleximod, test_repo, shared_repos): file_path = (test_repo / ".gitmodules") gm = shared_repos["gitmodules_content"] @@ -20,15 +21,15 @@ def test_required(git_fleximod, test_repo, shared_repos): result = git_fleximod(test_repo, "update") assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") - assert shared_repos["status3"] in status.stdout + assert shared_repos["status3"] in normalize_whitespace(status.stdout) status = git_fleximod(test_repo, f"update --optional") assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") - assert shared_repos["status4"] in status.stdout + assert shared_repos["status4"] in normalize_whitespace(status.stdout) status = git_fleximod(test_repo, f"update {repo_name}") assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") - assert shared_repos["status4"] in status.stdout + assert shared_repos["status4"] in normalize_whitespace(status.stdout) text = file_path.read_text() new_value = "somethingelse" diff --git a/.lib/git-fleximod/tests/test_d_complex.py b/.lib/git-fleximod/tests/test_d_complex.py index edde7d816..a91a2ed7f 100644 --- a/.lib/git-fleximod/tests/test_d_complex.py +++ b/.lib/git-fleximod/tests/test_d_complex.py @@ -1,24 +1,25 @@ import pytest from pathlib import Path from git_fleximod.gitinterface import GitInterface +from tests.utils_for_tests import normalize_whitespace def test_complex_checkout(git_fleximod, complex_repo, logger): status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) - assert("Complex not checked out, aligned at tag testtag02" in status.stdout) - assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex not checked out, aligned at tag testtag02" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # This should checkout and update test_submodule and complex_sub result = git_fleximod(complex_repo, "update") assert result.returncode == 0 status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag02" in status.stdout) + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag02" in normalize_whitespace(status.stdout)) # now check the complex_sub root = (complex_repo / "modules" / "complex") @@ -36,22 +37,22 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert result.returncode == 0 status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag02" in status.stdout) - assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag02" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # Finally update optional result = git_fleximod(complex_repo, "update --optional") assert result.returncode == 0 status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag02" in status.stdout) - assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag02" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional at tag MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # now check the complex_sub root = (complex_repo / "modules" / "complex" ) diff --git a/.lib/git-fleximod/tests/test_e_complex_update.py b/.lib/git-fleximod/tests/test_e_complex_update.py index 0c3ab4c6a..8b79b07c3 100644 --- a/.lib/git-fleximod/tests/test_e_complex_update.py +++ b/.lib/git-fleximod/tests/test_e_complex_update.py @@ -1,24 +1,25 @@ import pytest from pathlib import Path from git_fleximod.gitinterface import GitInterface +from tests.utils_for_tests import normalize_whitespace def test_complex_update(git_fleximod, complex_update, logger): status = git_fleximod(complex_update, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) - assert("Complex not checked out, out of sync at tag testtag02, expected tag is testtag3" in status.stdout) - assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex not checked out, out of sync at tag testtag02, expected tag is testtag3" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # This should checkout and update test_submodule and complex_sub result = git_fleximod(complex_update, "update") assert result.returncode == 0 status = git_fleximod(complex_update, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag3" in status.stdout) + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag3" in normalize_whitespace(status.stdout)) # now check the complex_sub root = (complex_update / "modules" / "complex") @@ -37,22 +38,22 @@ def test_complex_update(git_fleximod, complex_update, logger): assert result.returncode == 0 status = git_fleximod(complex_update, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag3" in status.stdout) - assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag3" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # Finally update optional result = git_fleximod(complex_update, "update --optional") assert result.returncode == 0 status = git_fleximod(complex_update, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag3" in status.stdout) - assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) + assert("ToplevelOptional at tag v5.3.2" in normalize_whitespace(status.stdout)) + assert("ToplevelRequired at tag MPIserial_2.5.0" in normalize_whitespace(status.stdout)) + assert("AlwaysRequired at tag MPIserial_2.4.0" in normalize_whitespace(status.stdout)) + assert("Complex at tag testtag3" in normalize_whitespace(status.stdout)) + assert("AlwaysOptional at tag MPIserial_2.3.0" in normalize_whitespace(status.stdout)) # now check the complex_sub root = (complex_update / "modules" / "complex" ) diff --git a/.lib/git-fleximod/tests/utils_for_tests.py b/.lib/git-fleximod/tests/utils_for_tests.py new file mode 100644 index 000000000..eb9ad88f7 --- /dev/null +++ b/.lib/git-fleximod/tests/utils_for_tests.py @@ -0,0 +1,13 @@ +""" +Helper functions that can be used in tests +""" + +def normalize_whitespace(text): + """ + Normalize whitespace for flexible string comparisons in tests. + + This removes leading and trailing whitespace and collapses other whitespace down to a + single space. + """ + return " ".join(text.split()) +