Esempio n. 1
0
 def build_branch_structure(self, model, skip):
     """Inspect and record the repo's branches and their history."""
     self._history = dict()
     self._history["commits"] = commits = dict()
     self._history["branches"] = branches = dict()
     for branch in self._repo.refs:
         LOGGER.debug(branch.name)
         if branch.name in skip:
             continue
         branches[branch.name] = branch_history = list()
         latest = branch.commit
         history = [latest] + list(latest.iter_parents())
         for commit in history:
             # Find model in committed files.
             if not is_modified(model, commit):
                 LOGGER.info(
                     "The model was not modified in commit '{}'. "
                     "Skipping.".format(commit))
                 continue
             branch_history.append(commit.hexsha)
             if commit.hexsha not in commits:
                 commits[commit.hexsha] = sub = dict()
                 sub["timestamp"] = commit.authored_datetime.isoformat(" ")
                 sub["author"] = commit.author.name
                 sub["email"] = commit.author.email
     LOGGER.debug("%s", json.dumps(self._history, indent=2))
Esempio n. 2
0
 def build_branch_structure(self, model, skip):
     """Inspect and record the repo's branches and their history."""
     self._history = dict()
     self._history["commits"] = commits = dict()
     self._history["branches"] = branches = dict()
     for branch in self._repo.refs:
         LOGGER.debug(branch.name)
         if branch.name in skip:
             continue
         branches[branch.name] = branch_history = list()
         latest = branch.commit
         history = [latest] + list(latest.iter_parents())
         for commit in history:
             # Find model in committed files.
             if not is_modified(model, commit):
                 LOGGER.info("The model was not modified in commit '{}'. "
                             "Skipping.".format(commit))
                 continue
             branch_history.append(commit.hexsha)
             if commit.hexsha not in commits:
                 commits[commit.hexsha] = sub = dict()
                 sub["timestamp"] = commit.authored_datetime.isoformat(" ")
                 sub["author"] = commit.author.name
                 sub["email"] = commit.author.email
     LOGGER.debug("%s", json.dumps(self._history, indent=2))
Esempio n. 3
0
def test_is_modified_deleted(mock_repo):
    """Don't report file deletion as modification."""
    relname, repo = mock_repo
    # File history (newest first): deleted, unchanged, modified, created
    # Don't report file deletion as "modification"
    # for purposes of running memote tests on the file.
    want = False, False, True, True
    # Convert to tuple so we can compare want == got,
    # which pytest will introspect helpfully if the assertion fails.
    got = tuple(utils.is_modified(relname, commit)
                for commit in repo.iter_commits())
    assert want == got
Esempio n. 4
0
def history(model,
            message,
            rewrite,
            solver,
            solver_timeout,
            location,
            pytest_args,
            deployment,
            commits,
            skip,
            exclusive,
            experimental=None):  # noqa: D301
    """
    Re-compute test results for the git branch history.

    MODEL is the path to the model file.

    MESSAGE is a commit message in case results were modified or added.

    [COMMIT] ... It is possible to list out individual commits that should be
    re-computed or supply a range <oldest commit>..<newest commit>, for example,

        memote history model.xml "chore: re-compute history" 6b84d05..cd49c85

    There are two distinct modes:

    \b
    1. Completely re-compute test results for each commit in the git history.
       This should only be necessary when memote is first used with existing
       model repositories.
    2. By giving memote specific commit hashes, it will re-compute test results
       for those only. This can also be achieved by supplying a commit range.

    """
    # callbacks.validate_path(model)
    callbacks.git_installed()
    if location is None:
        raise click.BadParameter("No 'location' given or configured.")
    if "--tb" not in pytest_args:
        pytest_args = ["--tb", "no"] + pytest_args
    try:
        LOGGER.info("Identifying git repository!")
        repo = git.Repo()
    except git.InvalidGitRepositoryError:
        LOGGER.critical(
            "The history requires a git repository in order to follow "
            "the model's commit history.")
        sys.exit(1)
    else:
        LOGGER.info("Success!")
        previous = repo.active_branch
        LOGGER.info("Checking out deployment branch {}.".format(deployment))
        repo.git.checkout(deployment)
    # Temporarily move the results to a new location so that they are
    # available while checking out the various commits.
    engine = None
    tmp_location = mkdtemp()
    try:
        # Test if the location can be opened as a database.
        engine = create_engine(location)
        engine.dispose()
        new_location = location
        if location.startswith("sqlite"):
            # Copy the SQLite database to a temporary location. Other
            # databases are not file-based and thus git independent.
            url = location.split("/", maxsplit=3)
            if isfile(url[3]):
                copy2(url[3], tmp_location)
            new_location = "{}/{}".format("/".join(url[:3] + [tmp_location]),
                                          url[3])
            LOGGER.info("Temporarily moving database from '%s' to '%s'.",
                        url[3], join(tmp_location, url[3]))
        manager = SQLResultManager(repository=repo, location=new_location)
    except (AttributeError, ArgumentError):
        LOGGER.info("Temporarily moving results from '%s' to '%s'.", location,
                    tmp_location)
        move(location, tmp_location)
        new_location = join(tmp_location, location)
        manager = RepoResultManager(repository=repo, location=new_location)
    LOGGER.info("Recomputing result history!")
    history = HistoryManager(repository=repo, manager=manager)
    history.load_history(model, skip={deployment})
    if len(commits) == 0:
        commits = list(history.iter_commits())
    elif len(commits) == 1 and ".." in commits[0]:
        commits = repo.git.rev_list(commits[0]).split(os.linesep)
    for commit in commits:
        cmt = repo.commit(commit)
        # Rewrite to full length hexsha.
        commit = cmt.hexsha
        if not is_modified(model, cmt):
            LOGGER.info("The model was not modified in commit '{}'. "
                        "Skipping.".format(commit))
            continue
        # Should we overwrite an existing result?
        if commit in history and not rewrite:
            LOGGER.info(
                "Result for commit '{}' exists. Skipping.".format(commit))
            continue
        LOGGER.info("Running the test suite for commit '{}'.".format(commit))
        blob = cmt.tree[model]
        model_obj, sbml_ver, notifications = _model_from_stream(
            blob.data_stream, blob.name)
        if model_obj is None:
            LOGGER.critical("The model could not be loaded due to the "
                            "following SBML errors.")
            stdout_notifications(notifications)
            continue
        proc = Process(target=_test_history,
                       args=(model_obj, sbml_ver, solver, solver_timeout,
                             manager, commit, pytest_args, skip, exclusive,
                             experimental))
        proc.start()
        proc.join()
    LOGGER.info("Finished recomputing!")
    # Copy back all new and modified files and add them to the index.
    LOGGER.info("Committing recomputed results!")
    repo.git.checkout(deployment)
    if engine is not None:
        manager.session.close()
        if location.startswith("sqlite"):
            copy2(join(tmp_location, url[3]), url[3])
    else:
        move(new_location, os.getcwd())
    repo.git.add(".")
    repo.git.commit("--message", message)
    LOGGER.info("Success!")
    # Checkout the original branch.
    previous.checkout()
    LOGGER.info("Done.")
Esempio n. 5
0
def run(model, collect, filename, location, ignore_git, pytest_args, exclusive,
        skip, solver, solver_timeout, experimental, custom_tests, deployment,
        skip_unchanged):
    """
    Run the test suite on a single model and collect results.

    MODEL: Path to model file. Can also be supplied via the environment variable
    MEMOTE_MODEL or configured in 'setup.cfg' or 'memote.ini'.

    """
    def is_verbose(arg):
        return (arg.startswith("--verbosity") or arg.startswith("-v")
                or arg.startswith("--verbose") or arg.startswith("-q")
                or arg.startswith("--quiet"))

    if ignore_git:
        repo = None
    else:
        callbacks.git_installed()
        repo = callbacks.probe_git()
    if collect:
        if repo is not None:
            if location is None:
                LOGGER.critical(
                    "Working with a repository requires a storage location.")
                sys.exit(1)
    if not any(a.startswith("--tb") for a in pytest_args):
        pytest_args = ["--tb", "short"] + pytest_args
    if not any(is_verbose(a) for a in pytest_args):
        pytest_args.append("-vv")
    # Check if the model was changed in this commit. Exit `memote run` if this
    # was not the case.
    if skip_unchanged and repo is not None:
        commit = repo.head.commit
        if not is_modified(model, commit):
            LOGGER.info("The model was not modified in commit '%s'. Skipping.",
                        commit.hexsha)
            sys.exit(0)
    # Add further directories to search for tests.
    pytest_args.extend(custom_tests)
    # Check if the model can be loaded at all.
    model, sbml_ver, notifications = api.validate_model(model)
    if model is None:
        LOGGER.critical(
            "The model could not be loaded due to the following SBML errors.")
        stdout_notifications(notifications)
        sys.exit(1)
    model.solver = solver
    code, result = api.test_model(model=model,
                                  sbml_version=sbml_ver,
                                  results=True,
                                  pytest_args=pytest_args,
                                  skip=skip,
                                  exclusive=exclusive,
                                  experimental=experimental,
                                  solver_timeout=solver_timeout)
    if collect:
        if repo is None:
            manager = ResultManager()
            manager.store(result, filename=filename)
        else:
            LOGGER.info("Checking out deployment branch.")
            # If the repo HEAD is pointing to the most recent branch then
            # GitPython's `repo.active_branch` works. Yet, if the repo is in
            # detached HEAD state, i.e., when a user has checked out a specific
            # commit as opposed to a branch, this won't work and throw a
            # `TypeError`, which we are circumventing below.
            try:
                previous = repo.active_branch
                previous_cmt = previous.commit
                is_branch = True
            except TypeError:
                previous_cmt = repo.head.commit
                is_branch = False
            repo.git.checkout(deployment)
            try:
                manager = SQLResultManager(repository=repo, location=location)
            except (AttributeError, ArgumentError):
                manager = RepoResultManager(repository=repo, location=location)
            LOGGER.info(
                "Committing result and changing back to working branch.")
            manager.store(result, commit=previous_cmt.hexsha)
            repo.git.add(".")
            repo.git.commit(
                "--message",
                "chore: add result for {}".format(previous_cmt.hexsha))
            if is_branch:
                previous.checkout()
            else:
                repo.commit(previous_cmt)
Esempio n. 6
0
def history(model, message, rewrite, solver, location, pytest_args, deployment,
            commits, skip, exclusive, experimental=None):  # noqa: D301
    """
    Re-compute test results for the git branch history.

    MODEL is the path to the model file.

    MESSAGE is a commit message in case results were modified or added.

    [COMMIT] ... It is possible to list out individual commits that should be
    re-computed or supply a range <oldest commit>..<newest commit>, for example,

        memote history model.xml "chore: re-compute history" 6b84d05..cd49c85

    There are two distinct modes:

    \b
    1. Completely re-compute test results for each commit in the git history.
       This should only be necessary when memote is first used with existing
       model repositories.
    2. By giving memote specific commit hashes, it will re-compute test results
       for those only. This can also be achieved by supplying a commit range.

    """
    # callbacks.validate_path(model)
    callbacks.git_installed()
    if location is None:
        raise click.BadParameter("No 'location' given or configured.")
    if "--tb" not in pytest_args:
        pytest_args = ["--tb", "no"] + pytest_args
    try:
        LOGGER.info("Identifying git repository!")
        repo = git.Repo()
    except git.InvalidGitRepositoryError:
        LOGGER.critical(
            "The history requires a git repository in order to follow "
            "the model's commit history.")
        sys.exit(1)
    else:
        LOGGER.info("Success!")
        previous = repo.active_branch
        LOGGER.info("Checking out deployment branch {}.".format(deployment))
        repo.git.checkout(deployment)
    # Temporarily move the results to a new location so that they are
    # available while checking out the various commits.
    engine = None
    tmp_location = mkdtemp()
    try:
        # Test if the location can be opened as a database.
        engine = create_engine(location)
        engine.dispose()
        new_location = location
        if location.startswith("sqlite"):
            # Copy the SQLite database to a temporary location. Other
            # databases are not file-based and thus git independent.
            url = location.split("/", maxsplit=3)
            if isfile(url[3]):
                copy2(url[3], tmp_location)
            new_location = "{}/{}".format(
                "/".join(url[:3] + [tmp_location]), url[3])
            LOGGER.info("Temporarily moving database from '%s' to '%s'.",
                        url[3], join(tmp_location, url[3]))
        manager = SQLResultManager(repository=repo, location=new_location)
    except (AttributeError, ArgumentError):
        LOGGER.info("Temporarily moving results from '%s' to '%s'.",
                    location, tmp_location)
        move(location, tmp_location)
        new_location = join(tmp_location, location)
        manager = RepoResultManager(repository=repo, location=new_location)
    LOGGER.info("Recomputing result history!")
    history = HistoryManager(repository=repo, manager=manager)
    history.load_history(model, skip={deployment})
    if len(commits) == 0:
        commits = list(history.iter_commits())
    elif len(commits) == 1 and ".." in commits[0]:
        commits = repo.git.rev_list(commits[0]).split(os.linesep)
    for commit in commits:
        cmt = repo.commit(commit)
        # Rewrite to full length hexsha.
        commit = cmt.hexsha
        if not is_modified(model, cmt):
            LOGGER.info(
                "The model was not modified in commit '{}'. "
                "Skipping.".format(commit))
            continue
        # Should we overwrite an existing result?
        if commit in history and not rewrite:
            LOGGER.info(
                "Result for commit '{}' exists. Skipping.".format(commit))
            continue
        LOGGER.info(
            "Running the test suite for commit '{}'.".format(commit))
        blob = cmt.tree[model]
        model_obj, sbml_ver, notifications = _model_from_stream(
            blob.data_stream, blob.name
        )
        if model_obj is None:
            LOGGER.critical("The model could not be loaded due to the "
                            "following SBML errors.")
            stdout_notifications(notifications)
            continue
        proc = Process(
            target=_test_history,
            args=(model_obj, sbml_ver, solver, manager, commit,
                  pytest_args, skip, exclusive, experimental))
        proc.start()
        proc.join()
    LOGGER.info("Finished recomputing!")
    # Copy back all new and modified files and add them to the index.
    LOGGER.info("Committing recomputed results!")
    repo.git.checkout(deployment)
    if engine is not None:
        manager.session.close()
        if location.startswith("sqlite"):
            copy2(join(tmp_location, url[3]), url[3])
    else:
        move(new_location, os.getcwd())
    repo.git.add(".")
    check_call(['git', 'commit', '-m', message])
    LOGGER.info("Success!")
    # Checkout the original branch.
    previous.checkout()
    LOGGER.info("Done.")
Esempio n. 7
0
def run(model, collect, filename, location, ignore_git, pytest_args, exclusive,
        skip, solver, experimental, custom_tests, deployment,
        skip_unchanged):
    """
    Run the test suite on a single model and collect results.

    MODEL: Path to model file. Can also be supplied via the environment variable
    MEMOTE_MODEL or configured in 'setup.cfg' or 'memote.ini'.

    """
    def is_verbose(arg):
        return (arg.startswith("--verbosity") or
                arg.startswith("-v") or arg.startswith("--verbose") or
                arg.startswith("-q") or arg.startswith("--quiet"))

    if ignore_git:
        repo = None
    else:
        callbacks.git_installed()
        repo = callbacks.probe_git()
    if collect:
        if repo is not None:
            if location is None:
                LOGGER.critical(
                    "Working with a repository requires a storage location.")
                sys.exit(1)
    if not any(a.startswith("--tb") for a in pytest_args):
        pytest_args = ["--tb", "short"] + pytest_args
    if not any(is_verbose(a) for a in pytest_args):
        pytest_args.append("-vv")
    # Check if the model was changed in this commit. Exit `memote run` if this
    # was not the case.
    if skip_unchanged and repo is not None:
        commit = repo.head.commit
        if not is_modified(model, commit):
            LOGGER.info("The model was not modified in commit '%s'. Skipping.",
                        commit.hexsha)
            sys.exit(0)
    # Add further directories to search for tests.
    pytest_args.extend(custom_tests)
    # Check if the model can be loaded at all.
    model, sbml_ver, notifications = api.validate_model(model)
    if model is None:
        LOGGER.critical(
            "The model could not be loaded due to the following SBML errors.")
        stdout_notifications(notifications)
        sys.exit(1)
    model.solver = solver
    # Load the experimental configuration using model information.
    if experimental is not None:
        experimental.load(model)
    code, result = api.test_model(
        model=model, sbml_version=sbml_ver, results=True,
        pytest_args=pytest_args, skip=skip,
        exclusive=exclusive, experimental=experimental)
    if collect:
        if repo is None:
            manager = ResultManager()
            manager.store(result, filename=filename)
        else:
            LOGGER.info("Checking out deployment branch.")
            # If the repo HEAD is pointing to the most recent branch then
            # GitPython's `repo.active_branch` works. Yet, if the repo is in
            # detached HEAD state, i.e., when a user has checked out a specific
            # commit as opposed to a branch, this won't work and throw a
            # `TypeError`, which we are circumventing below.
            try:
                previous = repo.active_branch
                previous_cmt = previous.commit
                is_branch = True
            except TypeError:
                previous_cmt = repo.head.commit
                is_branch = False
            repo.git.checkout(deployment)
            try:
                manager = SQLResultManager(repository=repo, location=location)
            except (AttributeError, ArgumentError):
                manager = RepoResultManager(repository=repo, location=location)
            LOGGER.info(
                "Committing result and changing back to working branch.")
            manager.store(result, commit=previous_cmt.hexsha)
            repo.git.add(".")
            check_call(
                ['git', 'commit',
                 '-m', "chore: add result for {}".format(previous_cmt.hexsha)]
            )
            if is_branch:
                previous.checkout()
            else:
                repo.commit(previous_cmt)