| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2020 The Android Open Source Project |
| # |
| # Licensed 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. |
| |
| from build_log_simplifier import collapse_consecutive_blank_lines |
| from build_log_simplifier import collapse_tasks_having_no_output |
| from build_log_simplifier import extract_task_names |
| from build_log_simplifier import remove_unmatched_exemptions |
| from build_log_simplifier import suggest_missing_exemptions |
| from build_log_simplifier import normalize_paths |
| from build_log_simplifier import regexes_matcher |
| from build_log_simplifier import remove_control_characters |
| import re |
| |
| def fail(message): |
| print(message) |
| exit(1) |
| |
| def test_regexes_matcher_get_matching_regexes(): |
| print("test_regexes_matcher_get_matching_regexes") |
| # For each of the given queries, we ask a regexes_matcher to identify which regexes |
| # match them, and compare to the right answer |
| queries = ["", "a", "aa", "aaa", "b", "bb", "ab", "c", "a*"] |
| regexes = ["a", "a*", "aa*", "b", "b*", "a*b"] |
| matcher = regexes_matcher(regexes) |
| for query in queries: |
| simple_matches = [regex for regex in regexes if re.compile(regex).fullmatch(query)] |
| fast_matches = matcher.get_matching_regexes(query) |
| if simple_matches != fast_matches: |
| fail("regexes_matcher returned incorrect results for '" + query + "'. Expected = " + str(simple_matches) + ", actual = " + str(fast_matches)) |
| print("Query = '" + query + "', matching regexes = " + str(simple_matches)) |
| |
| def test_normalize_paths(): |
| print("test_normalize_paths") |
| lines = [ |
| "CHECKOUT=/usr/home/me/workspace", |
| "/usr/home/me/workspace/external/protobuf/somefile.h: somewarning", |
| "Building CXX object protobuf-target/CMakeFiles/libprotobuf.dir/usr/home/me/workspace/external/somefile.cc" |
| ] |
| expected_normalized = [ |
| "CHECKOUT=$CHECKOUT", |
| "$CHECKOUT/external/protobuf/somefile.h: somewarning", |
| "Building CXX object protobuf-target/CMakeFiles/libprotobuf.dir$CHECKOUT/external/somefile.cc" |
| ] |
| actual = normalize_paths(lines) |
| if expected_normalized != actual: |
| fail("test_normalize_paths returned incorrect response.\n" + |
| "Input: " + str(lines) + "\n" + |
| "Output: " + str(actual) + "\n" + |
| "Expected output: " + str(expected_normalized) |
| ) |
| |
| def test_regexes_matcher_index_first_matching_regex(): |
| print("test_regexes_matcher_index_first_matching_regex") |
| regexes = ["first", "double", "single", "double"] |
| matcher = regexes_matcher(regexes) |
| assert(matcher.index_first_matching_regex("first") == 0) |
| assert(matcher.index_first_matching_regex("double") == 1) |
| assert(matcher.index_first_matching_regex("single") == 2) |
| assert(matcher.index_first_matching_regex("absent") is None) |
| |
| def test_detect_task_names(): |
| print("test_detect_task_names") |
| lines = [ |
| "> Task :one\n", |
| "some output\n", |
| "> Task :two\n", |
| "more output\n" |
| ] |
| task_names = [":one", ":two"] |
| detected_names = extract_task_names(lines) |
| if detected_names != task_names: |
| fail("extract_task_names returned incorrect response\n" + |
| "Input : " + str(lines) + "\n" + |
| "Output : " + str(detected_names) + "\n" + |
| "Expected: " + str(task_names) |
| ) |
| |
| def test_remove_unmatched_exemptions(): |
| print("test_remove_unmatched_exemptions") |
| lines = [ |
| "task two message one", |
| "task four message one", |
| ] |
| |
| current_config = [ |
| "# > Task :one", |
| "task one message one", |
| "# TODO(bug): remove this", |
| "# > Task :two", |
| "task two message one", |
| "# TODO(bug): remove this too", |
| "# > Task :three", |
| "task three message one", |
| "# > Task :four", |
| "task four message one", |
| "# TODO: maybe remove this too?", |
| "# > Task :five", |
| "task five message one" |
| ] |
| |
| expected_config = [ |
| "# TODO(bug): remove this", |
| "# > Task :two", |
| "task two message one", |
| "# > Task :four", |
| "task four message one", |
| ] |
| |
| actual_updated_config = remove_unmatched_exemptions(lines, current_config) |
| if actual_updated_config != expected_config: |
| fail("test_remove_unmatched_exemptions gave incorrect response.\n\n" + |
| "Input log : " + str(lines) + "\n\n" + |
| "Input config : " + str(current_config) + "\n\n" + |
| "Expected output config: " + str(expected_config) + "\n\n" + |
| "Actual output config : " + str(actual_updated_config)) |
| |
| def test_suggest_missing_exemptions(): |
| print("test_suggest_missing_exemptions") |
| lines = [ |
| "> Task :one", |
| "task one message one", |
| "task one message two", |
| "> Task :two", |
| "task two message one", |
| "duplicate line", |
| "> Task :three", |
| "task three message one", |
| "duplicate line" |
| ] |
| |
| expect_config = [ |
| "# > Task :one", |
| "task one message one", |
| "task one message two", |
| "# > Task :two", |
| "task two message one", |
| "duplicate line", |
| "# > Task :three", |
| "task three message one" |
| ] |
| |
| # generate config starting with nothing |
| validate_suggested_exemptions(lines, [], expect_config) |
| |
| # remove one line from config, regenerate config, line should return |
| config2 = expect_config[:1] + expect_config[2:] |
| validate_suggested_exemptions(lines, config2, expect_config) |
| |
| # if there is an existing config with the tasks in the other order, the tasks should stay in that order |
| # and the new line should be inserted after the previous matching line |
| config3 = [ |
| "# > Task :two", |
| "task two message one", |
| "duplicate line", |
| "# > Task :one", |
| "task one message two", |
| "# > Task :three", |
| "task three message one" |
| ] |
| expect_config3 = [ |
| "# > Task :two", |
| "task two message one", |
| "duplicate line", |
| "# > Task :one", |
| "task one message one", |
| "task one message two", |
| "# > Task :three", |
| "task three message one" |
| ] |
| validate_suggested_exemptions(lines, config3, expect_config3) |
| |
| # also validate that "> Configure project" gets ignored too |
| config4 = [ |
| "# > Configure project a", |
| "some warning" |
| ] |
| lines4 = [ |
| "> Configure project b", |
| "some warning" |
| ] |
| expect_config4 = config4 |
| validate_suggested_exemptions(lines4, config4, expect_config4) |
| |
| def test_collapse_tasks_having_no_output(): |
| print("test_collapse_tasks_having_no_output") |
| lines = [ |
| "> Task :no-output1", |
| "> Task :some-output1", |
| "output1", |
| "> Task :empty-output", |
| "", |
| "> Task :blanks-around-output", |
| "", |
| "output inside blanks", |
| "", |
| "> Task :no-output2", |
| "> Task :no-output3", |
| "FAILURE: Build failed with an exception.\n" |
| ] |
| expected = [ |
| "> Task :some-output1", |
| "output1", |
| "> Task :blanks-around-output", |
| "", |
| "output inside blanks", |
| "" |
| ] |
| actual = collapse_tasks_having_no_output(lines) |
| if (actual != expected): |
| fail("collapse_tasks_having_no_output gave incorrect error.\n" + |
| "Expected: " + str(expected) + "\n" + |
| "Actual = " + str(actual)) |
| |
| def test_collapse_consecutive_blank_lines(): |
| print("test_collapse_consecutive_blank_lines") |
| lines = [ |
| "", |
| "> Task :a", |
| "", |
| " ", |
| "\n\n", |
| "> Task :b", |
| " ", |
| "" |
| ] |
| expected_collapsed = [ |
| "> Task :a", |
| "", |
| "> Task :b", |
| " " |
| ] |
| actual_collapsed = collapse_consecutive_blank_lines(lines) |
| if actual_collapsed != expected_collapsed: |
| fail("collapse_consecutive_blank_lines returned incorrect response.\n" |
| "Input: " + lines + "\n" + |
| "Output: " + actual_collapsed + "\n" + |
| "Expected output: " + expected_collapsed |
| ) |
| |
| def validate_suggested_exemptions(lines, config, expected_config): |
| suggested_config = suggest_missing_exemptions(lines, config) |
| if suggested_config != expected_config: |
| fail("suggest_missing_exemptions incorrect response.\n" + |
| "Lines: " + str(lines) + ",\n" + |
| "config: " + str(config) + ",\n" + |
| "expected suggestion: " + str(expected_config) + ",\n" |
| "actual suggestion : " + str(suggested_config)) |
| |
| def test_remove_control_characters(): |
| print("test_remove_control_characters") |
| given = [ |
| # a line starting with several color codes in it |
| "[1msrc/main/java/androidx/arch/core/internal/FastSafeIterableMap.java:39: [33mwarning: [0mMethod androidx.arch.core.internal.FastSafeIterableMap.get(K) references hidden type androidx.arch.core.internal.SafeIterableMap.Entry<K,V>. [HiddenTypeParameter]", |
| # a line with a variety of characters, none of which are color codes |
| "space tab\tCAPITAL underscore_ slash/ colon: number 1 newline\n", |
| ] |
| expected = [ |
| "src/main/java/androidx/arch/core/internal/FastSafeIterableMap.java:39: warning: Method androidx.arch.core.internal.FastSafeIterableMap.get(K) references hidden type androidx.arch.core.internal.SafeIterableMap.Entry<K,V>. [HiddenTypeParameter]", |
| "space tab\tCAPITAL underscore_ slash/ colon: number 1 newline\n", |
| ] |
| actual = [remove_control_characters(line) for line in given] |
| if actual != expected: |
| fail("remove_control_charactres gave incorrect response.\n\n" + |
| "Input : " + str(given) + ".\n\n" + |
| "Expected output: " + str(expected) + ".\n\n" + |
| "Actual output : " + str(actual) + ".") |
| |
| |
| def main(): |
| test_collapse_consecutive_blank_lines() |
| test_collapse_tasks_having_no_output() |
| test_detect_task_names() |
| test_suggest_missing_exemptions() |
| test_normalize_paths() |
| test_regexes_matcher_get_matching_regexes() |
| test_regexes_matcher_index_first_matching_regex() |
| test_remove_control_characters() |
| test_remove_unmatched_exemptions() |
| |
| if __name__ == "__main__": |
| main() |