#!/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 "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. [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. [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()