-
Notifications
You must be signed in to change notification settings - Fork 12
Add script to check if package or its dependencies have changed for tests #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
90eca62
3df6f12
e0fbc66
2209efe
225a89f
679dbad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| This script determines whether one of the projects in the repo or any of its | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this really be in the top level directory? In fv3core we did use a
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If our top level directory gets cluttered I could see moving this eventually, but this is an integral part of running the top-level Makefile targets as we're currently planning to use it. If we call it directly in Jenkins instead of calling it inside the Makefile (as I did for test_util), we can move it then.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds good to me |
||
| dependencies are different from the version on the `main` branch. | ||
|
|
||
| This is useful for running tests only on projects that have changed. | ||
|
|
||
| This script should depend only on Python 3.6+ standard libraries. | ||
| """ | ||
| import argparse | ||
| import re | ||
| import os | ||
| import subprocess | ||
| from typing import Any, Dict, Set | ||
|
|
||
|
|
||
| DIRNAME = os.path.dirname(os.path.abspath(__file__)) | ||
| DEPENDENCIES_DOTFILE = os.path.join(DIRNAME, "dependencies.dot") | ||
ofuhrer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| DEFINITION_PATTERN = re.compile(r"\s*([a-zA-Z0-9]+) \[.*label=\"(.*)\".*\]") | ||
| DEPENDENCY_PATTERN = re.compile(r"\s*([a-zA-Z0-9]+) -> ([a-zA-Z0-9]+)") | ||
|
|
||
|
|
||
| def get_dependencies() -> Dict[str, Set[str]]: | ||
| name_to_subdir = {} | ||
| subdir_dependencies = {} | ||
| with open(DEPENDENCIES_DOTFILE, "r") as f: | ||
| dotfile_text = f.read() | ||
| for groups in DEFINITION_PATTERN.findall(dotfile_text): | ||
| name_to_subdir[groups[0]] = groups[1] | ||
| subdir_dependencies[groups[1]] = set() | ||
| for groups in DEPENDENCY_PATTERN.findall(dotfile_text): | ||
| name = groups[0] | ||
| dependency_name = groups[1] | ||
| if name in name_to_subdir: | ||
| subdir = name_to_subdir[name] | ||
| dependency_subdir = name_to_subdir[dependency_name] | ||
| subdir_dependencies[subdir].add(dependency_subdir) | ||
| add_nested_dependencies(subdir_dependencies) | ||
| return subdir_dependencies | ||
|
|
||
|
|
||
| def add_nested_dependencies(dependency_map: Dict[str, Set[str]]) -> Dict[str, Set[str]]: | ||
| """ | ||
| Given a dictionary mapping keys to dependent keys which may or may | ||
| not contain dependencies of dependencies, update it in-place so that | ||
| dependencies include all sub-dependencies. | ||
|
|
||
| Assumes the dependencies contain no cycles. | ||
| """ | ||
| # path can be at most as long as the total number of items | ||
| for _ in range(len(dependency_map)): | ||
| for dependencies in dependency_map.values(): | ||
| for dependent_key in dependencies: | ||
| dependencies.update(dependency_map[dependent_key]) | ||
|
|
||
|
|
||
| def parse_args(subdir_dependencies: Dict[str, Any]): | ||
| parser = argparse.ArgumentParser( | ||
| description=( | ||
| "Determines whether one of the projects in the repo or any of its " | ||
| "dependencies are different from the version on the `main` branch. " | ||
| "Prints \"false\" if the subdirectory and its dependencies are " | ||
| "unchanged, or \"true\" if they have changed." | ||
| ) | ||
| ) | ||
| parser.add_argument( | ||
| "project_name", type=str, help="subdirectory name of project to check", choices=subdir_dependencies.keys() | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def unstaged_files(dirname) -> bool: | ||
| result = subprocess.check_output( | ||
| ["git", "ls-files", "--other", "--directory", "--exclude-standard", dirname] | ||
| ) | ||
| return len(result) > 0 | ||
|
|
||
|
|
||
| def staged_files_changed(dirname) -> bool: | ||
| result = subprocess.check_output( | ||
| ["git", "diff", "main", dirname] | ||
| ) | ||
| return len(result) > 0 | ||
|
|
||
|
|
||
| def changed(dirname) -> bool: | ||
| return unstaged_files(dirname) or staged_files_changed(dirname) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| subdir_dependencies = get_dependencies() | ||
| args = parse_args(subdir_dependencies) | ||
| if changed(args.project_name): | ||
| print("true") | ||
| else: | ||
| for dependency_subdir in subdir_dependencies[args.project_name]: | ||
| if changed(dependency_subdir): | ||
| print("true") | ||
| break | ||
| else: | ||
| print("false") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,20 @@ | ||
| # this dotfile is used as a reference source for project dependencies | ||
| # each folder entry must have a "label" equal to its directory name | ||
| # | ||
| # If you update this file, please re-generate the svg with `make dependencies.svg` | ||
| # and commit it to the repository | ||
|
|
||
| digraph { | ||
| Pace [shape=box] | ||
| fv3core [shape=oval] | ||
| driver [shape=oval] | ||
| physics [shape=oval] | ||
| util [shape=oval] | ||
| pace [shape=box] | ||
| fv3core [shape=oval, label="fv3core"] | ||
| driver [shape=oval, label="driver"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. driver does not exist yet. should it already be here?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No harm in including it because it's not depended on by any other components, it can happen now or when the folder gets added. I believe it will be added as part of Rhea's current PR(s).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's fine if it is here. Right now it exits not very gracefully if you call |
||
| physics [shape=oval, label="fv3gfs-physics"] | ||
| util [shape=oval, label="fv3gfs-util"] | ||
|
|
||
| Pace -> fv3core | ||
| Pace -> physics | ||
| Pace -> util | ||
| Pace -> driver | ||
| pace -> fv3core | ||
| pace -> physics | ||
| pace -> util | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you actually need all of these dependencies explicitly? Or otherwise put, since the dependecy path pace -> fv3core -> util already exists, do we need pace -> util? In any case, we should make sure that for testing we do bottom -> up.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, pace and driver both directly depend on util, apart from their indirect dependence through other packages.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I get it. I guess my question is more related to what these dependencies actually imply w.r.t. testing order. Removing these dependencies could favor a bottom-up testing strategy (so we don't test pace right after util, but rather fv3core first).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All tests which need to run are run simultaneously. These dependencies are not used for testing order and do not imply anything w.r.t. testing order. |
||
| pace -> driver | ||
| driver -> fv3core | ||
| driver -> physics | ||
| driver -> util | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coming from a linux/unix environment, I have a tendency to make scripts like these executable and start with a
#!/usr/bin/env python3.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep the python here because hash bangs for python make me uncomfortable (do they use your active python env?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.