From c7a75e145e73c81e3207e1bfc50dee6b23d7ecc5 Mon Sep 17 00:00:00 2001 From: kai lin Date: Fri, 20 Feb 2026 11:56:33 -0500 Subject: [PATCH 1/2] added comprehensive install test --- tools/CI/install-test/CMakeLists.txt | 6 + tools/CI/install-test/all_services_test.cpp | 430 ++++++++++++++++++++ tools/scripts/codegen/install_test_gen.py | 140 +++++++ tools/scripts/run_code_generation.py | 12 + 4 files changed, 588 insertions(+) create mode 100644 tools/CI/install-test/all_services_test.cpp create mode 100644 tools/scripts/codegen/install_test_gen.py diff --git a/tools/CI/install-test/CMakeLists.txt b/tools/CI/install-test/CMakeLists.txt index ba0d5c7281d2..8091f30f6ac8 100644 --- a/tools/CI/install-test/CMakeLists.txt +++ b/tools/CI/install-test/CMakeLists.txt @@ -51,3 +51,9 @@ set(CMAKE_INSTALL_DOCDIR ${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}) install(TARGETS app RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + +# -- all_services_test build target -- +find_package(AWSSDK REQUIRED COMPONENTS core AWSMigrationHub accessanalyzer account acm acm-pca aiops amp amplify amplifybackend amplifyuibuilder apigateway apigatewaymanagementapi apigatewayv2 appconfig appconfigdata appfabric appflow appintegrations application-autoscaling application-insights application-signals applicationcostprofiler appmesh apprunner appstream appsync arc-region-switch arc-zonal-shift artifact athena auditmanager autoscaling autoscaling-plans awstransfer b2bi backup backup-gateway backupsearch batch bcm-dashboards bcm-data-exports bcm-pricing-calculator bcm-recommended-actions bedrock bedrock-agent bedrock-agent-runtime bedrock-agentcore bedrock-agentcore-control bedrock-data-automation bedrock-data-automation-runtime bedrock-runtime billing billingconductor braket budgets ce chatbot chime chime-sdk-identity chime-sdk-media-pipelines chime-sdk-meetings chime-sdk-messaging chime-sdk-voice cleanrooms cleanroomsml cloud9 cloudcontrol clouddirectory cloudformation cloudfront cloudfront-keyvaluestore cloudhsm cloudhsmv2 cloudsearch cloudsearchdomain cloudtrail cloudtrail-data codeartifact codebuild codecatalyst codecommit codeconnections codedeploy codeguru-reviewer codeguru-security codeguruprofiler codepipeline codestar-connections codestar-notifications cognito-identity cognito-idp cognito-sync comprehend comprehendmedical compute-optimizer compute-optimizer-automation config connect connect-contact-lens connectcampaigns connectcampaignsv2 connectcases connectparticipant controlcatalog controltower cost-optimization-hub cur customer-profiles databrew dataexchange datapipeline datasync datazone dax deadline detective devicefarm devops-guru directconnect directory-service-data discovery dlm dms docdb docdb-elastic drs ds dsql dynamodb dynamodbstreams ebs ec2 ec2-instance-connect ecr ecr-public ecs eks eks-auth elasticache elasticbeanstalk elasticfilesystem elasticloadbalancing elasticloadbalancingv2 elasticmapreduce email emr-containers emr-serverless entityresolution es eventbridge events evs finspace finspace-data firehose fis fms forecast forecastquery frauddetector freetier fsx gamelift gameliftstreams geo-maps geo-places geo-routes glacier globalaccelerator glue grafana greengrass greengrassv2 groundstation guardduty health healthlake iam identitystore imagebuilder importexport inspector inspector-scan inspector2 internetmonitor invoicing iot iot-data iot-jobs-data iot-managed-integrations iotdeviceadvisor iotevents iotevents-data iotfleetwise iotsecuretunneling iotsitewise iotthingsgraph iottwinmaker iotwireless ivs ivs-realtime ivschat kafka kafkaconnect kendra kendra-ranking keyspaces keyspacesstreams kinesis kinesis-video-archived-media kinesis-video-media kinesis-video-signaling kinesis-video-webrtc-storage kinesisanalytics kinesisanalyticsv2 kinesisvideo kms lakeformation lambda launch-wizard lex lex-models lexv2-models lexv2-runtime license-manager license-manager-linux-subscriptions license-manager-user-subscriptions lightsail location logs lookoutequipment m2 machinelearning macie2 mailmanager managedblockchain managedblockchain-query marketplace-agreement marketplace-catalog marketplace-deployment marketplace-entitlement marketplace-reporting marketplacecommerceanalytics mediaconnect mediaconvert medialive mediapackage mediapackage-vod mediapackagev2 mediastore mediastore-data mediatailor medical-imaging memorydb meteringmarketplace mgn migration-hub-refactor-spaces migrationhub-config migrationhuborchestrator migrationhubstrategy monitoring mpa mq mturk-requester mwaa mwaa-serverless neptune neptune-graph neptunedata network-firewall networkflowmonitor networkmanager networkmonitor notifications notificationscontacts nova-act oam observabilityadmin odb omics opensearch opensearchserverless organizations osis outposts panorama partnercentral-account partnercentral-benefits partnercentral-channel partnercentral-selling payment-cryptography payment-cryptography-data pca-connector-ad pca-connector-scep pcs personalize personalize-events personalize-runtime pi pinpoint pinpoint-email pinpoint-sms-voice-v2 pipes polly pricing proton qapps qbusiness qconnect quicksight ram rbin rds rds-data redshift redshift-data redshift-serverless rekognition repostspace resiliencehub resource-explorer-2 resource-groups resourcegroupstaggingapi rolesanywhere route53 route53-recovery-cluster route53-recovery-control-config route53-recovery-readiness route53domains route53globalresolver route53profiles route53resolver rtbfabric rum s3 s3-crt s3control s3outposts s3tables s3vectors sagemaker sagemaker-a2i-runtime sagemaker-edge sagemaker-featurestore-runtime sagemaker-geospatial sagemaker-metrics sagemaker-runtime sagemaker-runtime-http2 savingsplans scheduler schemas sdb secretsmanager security-ir securityhub securitylake serverlessrepo service-quotas servicecatalog servicecatalog-appregistry servicediscovery sesv2 shield signer signin simspaceweaver sms-voice snow-device-management snowball sns socialmessaging sqs ssm ssm-contacts ssm-guiconnect ssm-incidents ssm-quicksetup ssm-sap sso sso-admin sso-oidc states storagegateway sts supplychain support support-app swf synthetics taxsettings textract timestream-influxdb timestream-query timestream-write tnb transcribe transcribestreaming translate trustedadvisor verifiedpermissions voice-id vpc-lattice waf waf-regional wafv2 wellarchitected wickr wisdom workdocs workmail workmailmessageflow workspaces workspaces-instances workspaces-thin-client workspaces-web xray) +add_executable(all_services_test all_services_test.cpp) +target_link_libraries(all_services_test ${AWSSDK_LINK_LIBRARIES}) +target_include_directories(all_services_test PRIVATE ${AWSSDK_INCLUDE_DIRS}) diff --git a/tools/CI/install-test/all_services_test.cpp b/tools/CI/install-test/all_services_test.cpp new file mode 100644 index 000000000000..24370b16947b --- /dev/null +++ b/tools/CI/install-test/all_services_test.cpp @@ -0,0 +1,430 @@ +// Auto-generated by install_test_gen.py — do not edit manually. +// Compilation-only test: verifies every generated client header is includable. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + Aws::SDKOptions options; + Aws::InitAPI(options); + std::cout << "Compilation test succeeded\n"; + Aws::ShutdownAPI(options); + return 0; +} diff --git a/tools/scripts/codegen/install_test_gen.py b/tools/scripts/codegen/install_test_gen.py new file mode 100644 index 000000000000..03d65fbc3163 --- /dev/null +++ b/tools/scripts/codegen/install_test_gen.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +""" +Generates all_services_test.cpp (includes every generated client header) +and patches CMakeLists.txt to add the all_services_test executable. +""" + +import argparse +import os +import re + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +GENERATED_SRC = os.path.normpath(os.path.join(SCRIPT_DIR, "../../../generated/src")) +OUTPUT_CPP = os.path.join(SCRIPT_DIR, "../../CI/install-test/all_services_test.cpp") +CMAKE = os.path.join(SCRIPT_DIR, "../../CI/install-test/CMakeLists.txt") + +GENERATED_BLOCK_START = "# -- all_services_test build target --" + + +class InstallTestGen(object): + + def __init__(self, debug: bool, **kwargs): + self.debug = debug + + def generate(self, clients_to_build: set): + """Generate install tests for the given client list + + :param clients_to_build: Set of client names to generate tests for + :return: 0 if success, non-zero otherwise + """ + try: + client_list = list(clients_to_build) if clients_to_build else None + headers = self._find_client_headers(client_list) + components = [self._sdk_component(h) for h in headers] + self._write_cpp(headers) + self._patch_cmake(components) + + if self.debug: + if client_list: + print(f"Generated compilation test for {len(headers)} clients: {', '.join(client_list)}") + else: + print(f"Generated compilation test for all {len(headers)} clients") + return 0 + except Exception as e: + if self.debug: + print(f"Error generating install tests: {e}") + return -1 + + def _find_client_headers(self, client_list=None): + headers = [] + for dirpath, _, filenames in os.walk(GENERATED_SRC): + for fname in filenames: + if fname.endswith("Client.h"): + full = os.path.join(dirpath, fname) + parts = full.split(os.sep + "include" + os.sep, 1) + if len(parts) == 2: + header_path = parts[1].replace(os.sep, "/") + # Filter by client list if provided + if client_list: + service_name = self._sdk_component(header_path) + if service_name and service_name in client_list: + headers.append(header_path) + else: + headers.append(header_path) + return sorted(headers) + + def _sdk_component(self, header): + # "aws/s3-crt/S3CrtClient.h" -> "s3-crt" + parts = header.split("/") + return parts[1] if len(parts) >= 3 else None + + def _write_cpp(self, headers): + lines = [ + "// Auto-generated by install_test_gen.py — do not edit manually.", + "// Compilation-only test: verifies every generated client header is includable.", + "", + "#include ", + "#include ", + ] + lines += [f"#include <{h}>" for h in headers] + lines += [ + "", + "int main() {", + " Aws::SDKOptions options;", + " Aws::InitAPI(options);", + " std::cout << \"Compilation test succeeded\\n\";", + " Aws::ShutdownAPI(options);", + " return 0;", + "}", + "" + ] + with open(OUTPUT_CPP, "w") as f: + f.write("\n".join(lines)) + if self.debug: + print(f"Wrote {OUTPUT_CPP} ({len(headers)} headers)") + + def _patch_cmake(self, components): + comp_list = " ".join(sorted(set(filter(None, components)))) + block = ( + f"{GENERATED_BLOCK_START}\n" + f"find_package(AWSSDK REQUIRED COMPONENTS core {comp_list})\n" + f"add_executable(all_services_test all_services_test.cpp)\n" + f"target_link_libraries(all_services_test ${{AWSSDK_LINK_LIBRARIES}})\n" + f"target_include_directories(all_services_test PRIVATE ${{AWSSDK_INCLUDE_DIRS}})\n" + ) + with open(CMAKE, "r") as f: + content = f.read() + + pattern = re.compile( + re.escape(GENERATED_BLOCK_START) + r".*?(?=\n# |\Z)", + re.DOTALL, + ) + if pattern.search(content): + content = pattern.sub(block, content) + else: + content = content.rstrip("\n") + "\n\n" + block + "\n" + + with open(CMAKE, "w") as f: + f.write(content) + if self.debug: + print(f"Patched {CMAKE}") + +def parse_arguments(): + parser = argparse.ArgumentParser(description="Generate compilation test for AWS SDK C++ clients") + parser.add_argument("--client_list", type=str, help="Comma-separated or semi-colon-separated list of SDK clients") + return parser.parse_args() + +if __name__ == "__main__": + args = parse_arguments() + client_list = None + if args.client_list: + client_list = args.client_list.replace(";", ",").split(",") + client_list = [c.strip() for c in client_list if c.strip()] + + install_test_gen = InstallTestGen(debug=True) + exit_code = install_test_gen.generate(client_list) + exit(exit_code) \ No newline at end of file diff --git a/tools/scripts/run_code_generation.py b/tools/scripts/run_code_generation.py index b6796c142af6..c7858f092e84 100644 --- a/tools/scripts/run_code_generation.py +++ b/tools/scripts/run_code_generation.py @@ -15,6 +15,7 @@ from codegen.smoke_tests_gen import SmokeTestsGen from codegen.smithy_cpp_gen import SmithyCppGen from codegen.format_util import format_directories +from codegen.install_test_gen import InstallTestGen def parse_arguments() -> dict: @@ -64,6 +65,9 @@ def parse_arguments() -> dict: parser.add_argument("--generate_protocol_tests", help="Run protocol tests generation", action="store_true") + parser.add_argument("--generate_install_tests", + help="Run install test generation", + action="store_true") args = vars(parser.parse_args()) arg_map = {"debug": args.get("debug", False)} @@ -122,6 +126,7 @@ def parse_arguments() -> dict: arg_map["disable_virtual_operations"] = args.get("disable_virtual_operations", False) arg_map["generate_smoke_tests"] = args.get("generate_smoke_tests", None) arg_map["generate_protocol_tests"] = args.get("generate_protocol_tests", None) + arg_map["generate_install_tests"] = args.get("generate_install_tests", None) if arg_map["debug"]: print("args=", arg_map) return arg_map @@ -180,6 +185,13 @@ def main(): if existing_dirs: format_directories(existing_dirs) + # Generate install tests + if args["generate_install_tests"] and clients_to_build: + install_test_gen = InstallTestGen(args["debug"]) + if install_test_gen.generate(clients_to_build) != 0: + print("Error: Failed to generate install test") + return -1 + return 0 From f1c86228171e7dcfdc3c1466f45e31d4633f4d53 Mon Sep 17 00:00:00 2001 From: kai lin Date: Wed, 25 Feb 2026 13:44:29 -0500 Subject: [PATCH 2/2] addressed comments --- tools/scripts/codegen/install_test_gen.py | 39 +++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tools/scripts/codegen/install_test_gen.py b/tools/scripts/codegen/install_test_gen.py index 03d65fbc3163..793d0ab411fe 100644 --- a/tools/scripts/codegen/install_test_gen.py +++ b/tools/scripts/codegen/install_test_gen.py @@ -11,6 +11,8 @@ import argparse import os import re +from pathlib import Path +from typing import List, Optional, Set SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) GENERATED_SRC = os.path.normpath(os.path.join(SCRIPT_DIR, "../../../generated/src")) @@ -25,14 +27,14 @@ class InstallTestGen(object): def __init__(self, debug: bool, **kwargs): self.debug = debug - def generate(self, clients_to_build: set): + def generate(self, clients_to_build: Optional[Set[str]]): """Generate install tests for the given client list :param clients_to_build: Set of client names to generate tests for :return: 0 if success, non-zero otherwise """ try: - client_list = list(clients_to_build) if clients_to_build else None + client_list = clients_to_build if clients_to_build else None headers = self._find_client_headers(client_list) components = [self._sdk_component(h) for h in headers] self._write_cpp(headers) @@ -49,30 +51,27 @@ def generate(self, clients_to_build: set): print(f"Error generating install tests: {e}") return -1 - def _find_client_headers(self, client_list=None): + def _find_client_headers(self, client_list: Optional[Set[str]] = None) -> List[str]: headers = [] - for dirpath, _, filenames in os.walk(GENERATED_SRC): - for fname in filenames: - if fname.endswith("Client.h"): - full = os.path.join(dirpath, fname) - parts = full.split(os.sep + "include" + os.sep, 1) - if len(parts) == 2: - header_path = parts[1].replace(os.sep, "/") - # Filter by client list if provided - if client_list: - service_name = self._sdk_component(header_path) - if service_name and service_name in client_list: - headers.append(header_path) - else: - headers.append(header_path) + for path in Path(GENERATED_SRC).rglob("*Client.h"): + parts = path.parts + if "include" not in parts: + continue + header_path = "/".join(parts[parts.index("include") + 1:]) + if client_list: + service_name = self._sdk_component(header_path) + if service_name and service_name in client_list: + headers.append(header_path) + else: + headers.append(header_path) return sorted(headers) - def _sdk_component(self, header): + def _sdk_component(self, header: str) -> Optional[str]: # "aws/s3-crt/S3CrtClient.h" -> "s3-crt" parts = header.split("/") return parts[1] if len(parts) >= 3 else None - def _write_cpp(self, headers): + def _write_cpp(self, headers: List[str]) -> None: lines = [ "// Auto-generated by install_test_gen.py — do not edit manually.", "// Compilation-only test: verifies every generated client header is includable.", @@ -97,7 +96,7 @@ def _write_cpp(self, headers): if self.debug: print(f"Wrote {OUTPUT_CPP} ({len(headers)} headers)") - def _patch_cmake(self, components): + def _patch_cmake(self, components: List[Optional[str]]) -> None: comp_list = " ".join(sorted(set(filter(None, components)))) block = ( f"{GENERATED_BLOCK_START}\n"