| #!/usr/bin/env python3 |
| |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import os |
| import subprocess |
| import sys |
| |
| # this script does some primitive examination of git diff to determine if a test suite needs to be run or not |
| |
| # these jobs should always be run, no matter what |
| always_run_jobs = ['license checks', '(openjdk8) packaging check', '(openjdk11) packaging check'] |
| |
| # ignore changes to these files completely since they don't impact CI, if the changes are only to these files then all |
| # of CI can be skipped. however, jobs which are always run will still be run even if only these files are changed |
| ignore_prefixes = ['.github', '.idea', '.asf.yaml', '.backportrc.json', '.codecov.yml', '.dockerignore', '.gitignore', |
| '.lgtm.yml', 'CONTRIBUTING.md', 'setup-hooks.sh', 'upload.sh', 'dev', 'distribution/docker', |
| 'distribution/asf-release-process-guide.md', |
| 'owasp-dependency-check-suppressions.xml', 'licenses'] |
| |
| script_prefixes = ['check_test_suite.py', 'check_test_suite_test.py'] |
| script_job = ['script checks'] |
| |
| # these files are docs changes |
| # if changes are limited to this set then we can skip web-console and java |
| # if no changes in this set we can skip docs |
| docs_prefixes = ['docs/', 'website/'] |
| # travis docs job name |
| docs_jobs = ['docs'] |
| # these files are web-console changes |
| # if changes are limited to this set then we can skip docs and java |
| # if no changes in this set we can skip web-console |
| web_console_prefixes = ['web-console/'] |
| # travis web-console job name |
| web_console_jobs = ['web console', 'web console end-to-end test'] |
| web_console_still_run_for_java_jobs = ['web console end-to-end test'] |
| |
| |
| def check_ignore(file): |
| is_always_ignore = True in (file.startswith(prefix) for prefix in ignore_prefixes) |
| if is_always_ignore: |
| print("found ignorable file change: {}".format(file)) |
| return is_always_ignore |
| |
| def check_testable_script(file): |
| is_script = True in (file.startswith(prefix) for prefix in script_prefixes) |
| if is_script: |
| print("found script file change: {}".format(file)) |
| return is_script |
| |
| def check_docs(file): |
| is_docs = True in (file.startswith(prefix) for prefix in docs_prefixes) |
| if is_docs: |
| print("found docs file change: {}".format(file)) |
| return is_docs |
| |
| |
| def check_console(file): |
| is_console = True in (file.startswith(prefix) for prefix in web_console_prefixes) |
| if is_console: |
| print("found web-console file change: {}".format(file)) |
| return is_console |
| |
| |
| def check_should_run_suite(suite, diff_files): |
| """ |
| try to determine if a test suite should run or not given a set files in a diff |
| |
| :param suite: travis job name |
| :param diff_files: files changed in git diff |
| :return: True if the supplied suite needs to run, False otherwise |
| """ |
| |
| if suite in always_run_jobs: |
| # you gotta do what you gotta do |
| return True |
| |
| all_ignore = True |
| any_docs = False |
| all_docs = True |
| any_console = False |
| all_console = True |
| any_java = False |
| any_testable_script = False |
| all_testable_script = True |
| |
| # go over all of the files in the diff and collect some information about the diff contents, we'll use this later |
| # to decide whether or not to run the suite |
| for f in diff_files: |
| is_ignore = check_ignore(f) |
| all_ignore = all_ignore and is_ignore |
| is_docs = check_docs(f) |
| any_docs = any_docs or is_docs |
| all_docs = all_docs and is_docs |
| is_console = check_console(f) |
| any_console = any_console or is_console |
| all_console = all_console and is_console |
| is_script = check_testable_script(f) |
| any_testable_script = any_testable_script or is_script |
| all_testable_script = all_testable_script and is_script |
| any_java = any_java or (not is_ignore and not is_docs and not is_console and not is_script) |
| |
| # if everything is ignorable, we can skip this suite |
| if all_ignore: |
| return False |
| # if the test suite is a doc job, return true if any of the files changed were docs |
| if suite in docs_jobs: |
| return any_docs |
| # if all of the changes are docs paths, but the current suite is not a docs job, we can skip |
| if all_docs: |
| return False |
| if suite in web_console_still_run_for_java_jobs: |
| return any_console or any_java |
| # if the test suite is a web console job, return true if any of the changes are web console files |
| if suite in web_console_jobs: |
| return any_console |
| # if all of the changes are web console paths, but the current suite is not a web console job, we can skip |
| if all_console: |
| return False |
| if suite in script_job: |
| return any_testable_script |
| if all_testable_script: |
| return False |
| |
| # if all of the files belong to known non-java groups, we can also skip java |
| # note that this should probably be reworked to much more selectively run the java jobs depending on the diff |
| if not any_java: |
| return False |
| |
| # we don't know we can skip for sure, so lets run it |
| return True |
| |
| |
| def failWithUsage(): |
| sys.stderr.write("usage: check_test_suite.py <test-suite-name>\n") |
| sys.stderr.write(" e.g., check_test_suite.py docs") |
| sys.exit(1) |
| |
| |
| if __name__ == '__main__': |
| suite_name = "" |
| |
| # when run by travis, we run this script without arguments, so collect the test suite name from environment |
| # variables. if it doesn't exist, fail |
| if len(sys.argv) == 1: |
| if 'TRAVIS_JOB_NAME' in os.environ: |
| suite_name = os.environ['TRAVIS_JOB_NAME'] |
| else: |
| failWithUsage() |
| elif len(sys.argv) == 2: |
| # to help with testing, can explicitly pass a test suite name |
| suite_name = sys.argv[1] |
| else: |
| failWithUsage() |
| |
| # we only selectively run CI for PR builds, branch builds such as master and releases will always run all suites |
| is_pr = False |
| if 'TRAVIS_PULL_REQUEST' in os.environ and os.environ['TRAVIS_PULL_REQUEST'] != 'false': |
| is_pr = True |
| |
| if not is_pr: |
| print("Not a pull request build, need to run all test suites") |
| sys.exit(1) |
| |
| # this looks like it only gets the last commit, but the way travis PR builds work this actually gets the complete |
| # diff (since all commits from the PR are rebased onto the target branch and added as a single commit) |
| all_changed_files_string = subprocess.check_output("git diff --name-only HEAD~1", shell=True).decode('UTF-8') |
| all_changed_files = all_changed_files_string.splitlines() |
| print("Checking if suite '{}' needs to run test on diff:\n{}".format(suite_name, all_changed_files_string)) |
| |
| # we should always run all test suites for builds that are not for a pull request |
| needs_run = check_should_run_suite(suite_name, all_changed_files) |
| |
| if needs_run: |
| print("Changes detected, need to run test suite '{}'".format(suite_name)) |
| sys.exit(1) |
| |
| print("No applicable changes detected, can skip test suite '{}'".format(suite_name)) |
| sys.exit(0) |