From c008bf9a1e046d6ea86bcf823c79c86ef85def52 Mon Sep 17 00:00:00 2001 From: Joe Wang Date: Thu, 19 Dec 2024 14:16:24 -0500 Subject: [PATCH] chore: remove IT on workflow --- .../workflows/verify_library_generation.yaml | 23 -- .../library_generation/tests/compare_poms.py | 131 ------- .../tests/integration_tests.py | 371 ------------------ .../integration/test_generator_coordinates | 1 - 4 files changed, 526 deletions(-) delete mode 100644 hermetic_build/library_generation/tests/compare_poms.py delete mode 100644 hermetic_build/library_generation/tests/integration_tests.py delete mode 100644 hermetic_build/library_generation/tests/resources/integration/test_generator_coordinates diff --git a/.github/workflows/verify_library_generation.yaml b/.github/workflows/verify_library_generation.yaml index fc6759f50e..cb229bbfba 100644 --- a/.github/workflows/verify_library_generation.yaml +++ b/.github/workflows/verify_library_generation.yaml @@ -38,29 +38,6 @@ jobs: head_repo_url: ${{ github.event.pull_request.head.repo.html_url }} head_repo_name: ${{ github.event.pull_request.head.repo.full_name }} base_repo: ${{ github.repository }} - library-generation-integration-tests: - runs-on: ubuntu-22.04 - needs: should-run-library-generation-tests - if: needs.should-run-library-generation-tests.outputs.should_run == 'true' - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - name: install python modules and dependencies - shell: bash - run: | - set -ex - pip install --upgrade pip - pip install --require-hashes -r hermetic_build/common/requirements.txt - pip install hermetic_build/common - pip install --require-hashes -r hermetic_build/library_generation/requirements.txt - pip install hermetic_build/library_generation - - name: Run integration tests - shell: bash - run: | - set -x - python -m unittest hermetic_build/library_generation/tests/integration_tests.py library-generation-unit-tests: runs-on: ubuntu-22.04 needs: should-run-library-generation-tests diff --git a/hermetic_build/library_generation/tests/compare_poms.py b/hermetic_build/library_generation/tests/compare_poms.py deleted file mode 100644 index 95041651fb..0000000000 --- a/hermetic_build/library_generation/tests/compare_poms.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Utility to compare the contents of two XML files. -This focuses on the tree structure of both XML files, meaning that element order and whitespace will be disregarded. -The only comparison points are: element path (e.g. project/dependencies) and element text -There is a special case for `dependency`, where the maven coordinates are prepared as well -""" - -from library_generation.utils.utilities import eprint -import xml.etree.ElementTree as et -from collections import Counter -import sys -import os - -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - - -def get_text_from_element(node, element_name, namespace): - """ - Convenience method to access a node's child elements via path and get - its text. - """ - child = node.find(namespace + element_name) - return child.text if child is not None else "" - - -def print_counter(counter): - """ - Convenience method to pretty print the contents of a Counter (or dict) - """ - for key, value in counter.items(): - eprint(f"{key}: {value}") - - -def append_to_element_list(node, path, elements): - """ - Recursively traverses a node tree and appends element text to a given - `elements` array. If the element tag is `dependency` - then the maven coordinates for its children will be computed as well - """ - namespace_start, namespace_end, tag_name = node.tag.rpartition("}") - namespace = namespace_start + namespace_end - if tag_name == "dependency": - group_id = get_text_from_element(node, "groupId", namespace) - artifact_id = get_text_from_element(node, "artifactId", namespace) - artifact_str = "" - artifact_str += group_id - artifact_str += ":" + artifact_id - elements.append(path + "/" + tag_name + "=" + artifact_str) - if node.text and len(node.text.strip()) > 0: - elements.append(path + "/" + tag_name + "=" + node.text) - - if tag_name == "version": - # versions may be yet to be processed, we disregard them - return elements - - for child in node: - child_path = path + "/" + tag_name - append_to_element_list(child, child_path, elements) - - return elements - - -def compare_xml(expected, actual, print_trees): - """ - compares two XMLs for content differences - the argument print_whole_trees determines if both trees should be printed - """ - try: - expected_tree = et.parse(expected) - actual_tree = et.parse(actual) - except et.ParseError as e: - eprint(f"Error parsing XML") - raise e - except FileNotFoundError as e: - eprint(f"Error reading file") - raise e - - expected_elements = [] - actual_elements = [] - - append_to_element_list(expected_tree.getroot(), "/", expected_elements) - append_to_element_list(actual_tree.getroot(), "/", actual_elements) - - expected_counter = Counter(expected_elements) - actual_counter = Counter(actual_elements) - intersection = expected_counter & actual_counter - only_in_expected = expected_counter - intersection - only_in_actual = actual_counter - intersection - if print_trees: - eprint("expected") - print_counter(actual_counter) - eprint("actual") - print_counter(expected_counter) - if len(only_in_expected) > 0 or len(only_in_actual) > 0: - eprint("only in " + expected) - print_counter(only_in_expected) - eprint("only in " + actual) - print_counter(only_in_actual) - return True - return False - - -def compare_pom_in_subdir(base_dir: str, subdir: str) -> bool: - golden = os.path.join(base_dir, subdir, "pom-golden.xml") - pom = os.path.join(base_dir, subdir, "pom.xml") - return compare_xml( - golden, - pom, - False, - ) - - -if __name__ == "__main__": - if len(sys.argv) != 4: - eprint( - "Usage: python compare_xml.py " - ) - sys.exit(1) - - file1 = sys.argv[1] - file2 = sys.argv[2] - print_whole_trees = sys.argv[3] - has_diff = compare_xml(file1, file2, print_whole_trees) - - if has_diff: - eprint(f"The poms are different") - sys.exit(1) - eprint("The XML files are the same.") - sys.exit(0) diff --git a/hermetic_build/library_generation/tests/integration_tests.py b/hermetic_build/library_generation/tests/integration_tests.py deleted file mode 100644 index f738a92093..0000000000 --- a/hermetic_build/library_generation/tests/integration_tests.py +++ /dev/null @@ -1,371 +0,0 @@ -# Copyright 2024 Google LLC -# -# 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 -# -# https://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 difflib -import json -import tempfile -from filecmp import dircmp -from git import Repo -import os -import shutil -import subprocess -import unittest -from pathlib import Path -from common.model.generation_config import GenerationConfig -from common.model.generation_config import from_yaml -from library_generation.tests.compare_poms import compare_xml -from library_generation.utils.utilities import sh_util as shell_call - - -script_dir = os.path.dirname(os.path.realpath(__file__)) -config_dir = os.path.join(script_dir, "resources", "integration") -golden_dir = os.path.join(config_dir, "golden") -generator_jar_coordinates_file = os.path.join(config_dir, "test_generator_coordinates") -repo_root_dir = os.path.join(script_dir, "..", "..", "..") -build_file = os.path.join( - repo_root_dir, ".cloudbuild", "library_generation", "library_generation.Dockerfile" -) -image_tag = "test-image:latest" -repo_prefix = "https://github.com/googleapis" -output_dir = shell_call("get_output_folder") -# this map tells which branch of each repo should we use for our diff tests -commitish_map = { - "google-cloud-java": "chore/test-hermetic-build", - "java-bigtable": "chore/test-hermetic-build", -} -generation_config_name = "generation_config.yaml" -googleapis_commitish = "113a378d5aad5018876ec0a8cbfd4d6a4f746809" -# This variable is used to override the jar created by building the image -# with our own downloaded jar in order to lock the integration test to use -# a constant version specified in -# library_generation/test/resources/integration/test_generator_coordinates -# This allows us to decouple the generation workflow testing with what the -# generator jar will actually generate. -# See library_generation/DEVELOPMENT.md ("The Hermetic Build's -# well-known folder"). -WELL_KNOWN_GENERATOR_JAR_FILENAME = "gapic-generator-java.jar" - - -class IntegrationTest(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.__download_generator_jar(coordinates_file=generator_jar_coordinates_file) - cls.__build_image(docker_file=build_file, cwd=repo_root_dir) - - @classmethod - def setUp(cls) -> None: - cls.__remove_generated_files() - os.makedirs(f"{golden_dir}", exist_ok=True) - - def test_entry_point_running_in_container(self): - api_definitions_path = self.__copy_api_definition(googleapis_commitish) - config_files = self.__get_config_files(config_dir) - for repo, config_file in config_files: - config = from_yaml(config_file) - repo_location = f"{output_dir}/{repo}" - config_location = f"{golden_dir}/../{repo}" - # 1. pull repository - repo_dest = self.__pull_repo_to( - Path(repo_location), repo, commitish_map[repo] - ) - # 2. prepare golden files - library_names = self.__get_library_names_from_config(config) - self.__prepare_golden_files( - config=config, library_names=library_names, repo_dest=repo_dest - ) - # 3. run entry_point.py in docker container - self.__run_entry_point_in_docker_container( - repo_location=repo_location, - config_location=config_location, - generation_config=generation_config_name, - api_definition=api_definitions_path, - ) - # 4. compare generation result with golden files - print( - "Generation finished successfully. " - "Will now compare differences between generated and existing " - "libraries" - ) - for library_name in library_names: - actual_library = ( - f"{repo_dest}/{library_name}" if config.is_monorepo() else repo_dest - ) - print("*" * 50) - print(f"Checking for differences in '{library_name}'.") - print(f" The expected library is in {golden_dir}/{library_name}.") - print(f" The actual library is in {actual_library}. ") - compare_result = dircmp( - f"{golden_dir}/{library_name}", - actual_library, - ignore=[".repo-metadata.json"], - ) - diff_files = [] - golden_only = [] - generated_only = [] - # compare source code - self.__recursive_diff_files( - compare_result, diff_files, golden_only, generated_only - ) - - # print all found differences for inspection - print_file = lambda f: print(f" - {f}") - if len(diff_files) > 0: - print(" Some files (found in both folders) are differing:") - for diff_file in diff_files: - print(f"Difference in {diff_file}:") - with open( - f"{golden_dir}/{library_name}/{diff_file}" - ) as expected_file: - with open(f"{actual_library}/{diff_file}") as actual_file: - [ - print(line) - for line in difflib.unified_diff( - expected_file.readlines(), - actual_file.readlines(), - ) - ] - if len(golden_only) > 0: - print(" There were files found only in the golden dir:") - [print_file(f) for f in golden_only] - if len(generated_only) > 0: - print(" There were files found only in the generated dir:") - [print_file(f) for f in generated_only] - - self.assertTrue(len(golden_only) == 0) - self.assertTrue(len(generated_only) == 0) - self.assertTrue(len(diff_files) == 0) - - print(f" No differences found in {library_name}") - # compare .repo-metadata.json - self.assertTrue( - self.__compare_json_files( - f"{golden_dir}/{library_name}/.repo-metadata.json", - f"{actual_library}/.repo-metadata.json", - ), - msg=f" The generated {library_name}/.repo-metadata.json is different from golden.", - ) - print(" .repo-metadata.json comparison succeed.") - - if not config.is_monorepo(): - continue - - # compare gapic-libraries-bom/pom.xml and pom.xml - self.assertFalse( - compare_xml( - f"{golden_dir}/gapic-libraries-bom/pom.xml", - f"{repo_dest}/gapic-libraries-bom/pom.xml", - False, - ) - ) - print(" gapic-libraries-bom/pom.xml comparison succeed.") - self.assertFalse( - compare_xml( - f"{golden_dir}/pom.xml", - f"{repo_dest}/pom.xml", - False, - ) - ) - print(" pom.xml comparison succeed.") - self.__remove_generated_files() - shutil.rmtree(api_definitions_path) - - @classmethod - def __copy_api_definition(cls, committish: str) -> str: - repo_dest = cls.__pull_repo_to( - dest=tempfile.mkdtemp(), repo="googleapis", committish=committish - ) - api_temp_dir = tempfile.mkdtemp() - print(f"Copying api definition to {api_temp_dir}...") - shutil.copytree( - f"{repo_dest}/google", f"{api_temp_dir}/google", dirs_exist_ok=True - ) - shutil.copytree( - f"{repo_dest}/grafeas", f"{api_temp_dir}/grafeas", dirs_exist_ok=True - ) - shutil.rmtree(repo_dest) - return api_temp_dir - - @classmethod - def __build_image(cls, docker_file: str, cwd: str): - # we build the docker image without removing intermediate containers, so - # we can re-test more quickly - subprocess.check_call( - ["docker", "build", "-f", docker_file, "-t", image_tag, "."], - cwd=cwd, - env=dict(os.environ, DOCKER_BUILDKIT="1"), - ) - - @classmethod - def __download_generator_jar(cls, coordinates_file: str) -> None: - """ - Downloads the jar at the version specified in the - coordinates file - :param coordinates_file: path to the file containing the coordinates - """ - with open(coordinates_file, "r") as coordinates_file_handle: - # make this var available in the function scope - # nonlocal coordinates - coordinates = coordinates_file_handle.read() - # download the jar - subprocess.check_call( - [ - "mvn", - "dependency:copy", - "-B", - "-ntp", - f"-Dartifact={coordinates}", - f"-DoutputDirectory={config_dir}", - ] - ) - - # compute the filename of the downloaded jar - split_coordinates = coordinates.split(":") - artifact_id = split_coordinates[1] - version = split_coordinates[2] - jar_filename = f"{artifact_id}-{version}.jar" - - # rename the jar to its well-known filename defined at the top of this - # script file - source_jar_path = os.path.join(config_dir, jar_filename) - destination_jar_path = os.path.join( - config_dir, WELL_KNOWN_GENERATOR_JAR_FILENAME - ) - shutil.move(source_jar_path, destination_jar_path) - - @classmethod - def __remove_generated_files(cls): - shutil.rmtree(f"{output_dir}", ignore_errors=True) - if os.path.isdir(f"{golden_dir}"): - shutil.rmtree(f"{golden_dir}") - - @classmethod - def __pull_repo_to(cls, dest: Path, repo: str, committish: str) -> str: - shutil.rmtree(dest, ignore_errors=True) - repo_url = f"{repo_prefix}/{repo}" - print(f"Cloning repository {repo_url}") - repo = Repo.clone_from(repo_url, dest) - repo.git.checkout(committish) - return str(dest) - - @classmethod - def __get_library_names_from_config(cls, config: GenerationConfig) -> list[str]: - library_names = [] - for library in config.libraries: - library_names.append(f"java-{library.get_library_name()}") - - return library_names - - @classmethod - def __prepare_golden_files( - cls, config: GenerationConfig, library_names: list[str], repo_dest: str - ): - for library_name in library_names: - if config.is_monorepo(): - shutil.copytree( - f"{repo_dest}/{library_name}", f"{golden_dir}/{library_name}" - ) - shutil.copytree( - f"{repo_dest}/gapic-libraries-bom", - f"{golden_dir}/gapic-libraries-bom", - dirs_exist_ok=True, - ) - shutil.copyfile(f"{repo_dest}/pom.xml", f"{golden_dir}/pom.xml") - else: - shutil.copytree(f"{repo_dest}", f"{golden_dir}/{library_name}") - - @classmethod - def __run_entry_point_in_docker_container( - cls, - repo_location: str, - config_location: str, - generation_config: str, - api_definition: str, - ): - # we use the calling user to prevent the mapped volumes from changing - # owners - user_id = shell_call("id -u") - group_id = shell_call("id -g") - subprocess.check_call( - [ - "docker", - "run", - "-u", - f"{user_id}:{group_id}", - "--rm", - "--quiet", - "--network", - "none", - "-v", - f"{repo_location}:/workspace/repo", - "-v", - f"{config_location}:/workspace/config", - "-v", - f"{api_definition}:/workspace/api", - "-v", - f"{config_dir}/{WELL_KNOWN_GENERATOR_JAR_FILENAME}:/home/.library_generation/{WELL_KNOWN_GENERATOR_JAR_FILENAME}", - "-w", - "/workspace/repo", - image_tag, - f"--generation-config-path=/workspace/config/{generation_config}", - f"--api-definitions-path=/workspace/api", - ], - ) - - @classmethod - def __get_config_files(cls, path: str) -> list[tuple[str, str]]: - config_files = [] - for sub_dir in Path(path).resolve().iterdir(): - if sub_dir.is_file(): - continue - repo = sub_dir.name - if repo in ["golden", "java-bigtable"]: - continue - config = f"{sub_dir}/{generation_config_name}" - config_files.append((repo, config)) - return config_files - - @classmethod - def __compare_json_files(cls, expected: str, actual: str) -> bool: - return cls.__load_json_to_sorted_list( - expected - ) == cls.__load_json_to_sorted_list(actual) - - @classmethod - def __load_json_to_sorted_list(cls, path: str) -> list[tuple]: - with open(path) as f: - data = json.load(f) - res = [(key, value) for key, value in data.items()] - - return sorted(res, key=lambda x: x[0]) - - @classmethod - def __recursive_diff_files( - cls, - dcmp: dircmp, - diff_files: list[str], - left_only: list[str], - right_only: list[str], - dirname: str = "", - ): - """ - Recursively compares two subdirectories. The found differences are - passed to three expected list references. - """ - append_dirname = lambda d: dirname + d - diff_files.extend(map(append_dirname, dcmp.diff_files)) - left_only.extend(map(append_dirname, dcmp.left_only)) - right_only.extend(map(append_dirname, dcmp.right_only)) - for sub_dirname, sub_dcmp in dcmp.subdirs.items(): - cls.__recursive_diff_files( - sub_dcmp, diff_files, left_only, right_only, dirname + sub_dirname + "/" - ) diff --git a/hermetic_build/library_generation/tests/resources/integration/test_generator_coordinates b/hermetic_build/library_generation/tests/resources/integration/test_generator_coordinates deleted file mode 100644 index 00af5f647f..0000000000 --- a/hermetic_build/library_generation/tests/resources/integration/test_generator_coordinates +++ /dev/null @@ -1 +0,0 @@ -com.google.api:gapic-generator-java:2.38.1 \ No newline at end of file