diff --git a/.gitignore b/.gitignore index a914f9a..4620c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ sdks/typescript/sample/dist/ # Python SDK specific sdks/python/src/test_server_sdk/__pycache__ sdks/python/__pycache__ -sdks/python/test_server_sdk.egg-info/ +sdks/python/src/test_server_sdk.egg-info # Python SDK Sample specific sdks/python/sample/__pycache__ diff --git a/sdks/python/MANIFEST.in b/sdks/python/MANIFEST.in index d83c02d..5bcc16b 100644 --- a/sdks/python/MANIFEST.in +++ b/sdks/python/MANIFEST.in @@ -1,6 +1,4 @@ include LICENSE include README.md -include install.py -include checksums.json prune sample diff --git a/sdks/python/README.md b/sdks/python/README.md index 402a2ab..2e2cd9d 100644 --- a/sdks/python/README.md +++ b/sdks/python/README.md @@ -15,7 +15,7 @@ pip3 install -r requirements.txt ```sh # Ensure a clean build -rm -rf build/ dist/ *.egg-info/ src/test_server_sdk/bin/ && find . -depth -name "__pycache__" -type d -exec rm -rf {} \; +rm -rf build/ dist/ src/*.egg-info/ *.egg-info && find . -depth -name "__pycache__" -type d -exec rm -rf {} \; # Build the python wheel python3 -m build ``` @@ -25,13 +25,17 @@ python3 -m build ## Installation of the Python Test Server sdk ```sh +# Ensure a clean installation +pip3 uninstall test-server-sdk -y # Install from the dist, use --force-reinstall to alwasy install fresh -pip install --force-reinstall dist/test_server_sdk-0.1.0-py3-none-any.whl +pip3 install --force-reinstall dist/test_server_sdk-0.1.0-py3-none-any.whl +# This is the command to download and verify the underlying golang executabel. +download_golang_executable # Check on the files pip show -f test_server_sdk ``` -You should see something very similar to this output, note tehat the `test_server_sdk/bin/` folder exist and contains the golang test-server: +You should see something very similar to this output, note tehat the `../../../bin/download_golang_executable` entry exist. ``` Name: test-server-sdk Version: 0.1.0 @@ -44,25 +48,22 @@ Location: /usr/local/google/home/wanlindu/env/lib/python3.13/site-packages Requires: PyYAML, requests Required-by: Files: - src/test_server_sdk/__init__.py - src/test_server_sdk/__pycache__/__init__.cpython-313.pyc - src/test_server_sdk/__pycache__/test_server_wrapper.cpython-313.pyc - src/test_server_sdk/test_server_wrapper.py + ../../../bin/download_golang_executable test_server_sdk-0.1.0.dist-info/INSTALLER test_server_sdk-0.1.0.dist-info/METADATA test_server_sdk-0.1.0.dist-info/RECORD test_server_sdk-0.1.0.dist-info/REQUESTED test_server_sdk-0.1.0.dist-info/WHEEL test_server_sdk-0.1.0.dist-info/direct_url.json + test_server_sdk-0.1.0.dist-info/entry_points.txt test_server_sdk-0.1.0.dist-info/licenses/LICENSE test_server_sdk-0.1.0.dist-info/top_level.txt test_server_sdk/__init__.py test_server_sdk/__pycache__/__init__.cpython-313.pyc + test_server_sdk/__pycache__/install.cpython-313.pyc test_server_sdk/__pycache__/test_server_wrapper.cpython-313.pyc - test_server_sdk/bin/CHANGELOG.md - test_server_sdk/bin/LICENSE - test_server_sdk/bin/README.md - test_server_sdk/bin/test-server + test_server_sdk/checksums.json + test_server_sdk/install.py test_server_sdk/test_server_wrapper.py ``` diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 38cf382..70487c4 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -28,9 +28,8 @@ dependencies = [ Homepage = "https://github.com/google/test-server/sdks/python" Issues = "https://github.com/google/test-server/issues" -[tool.setuptools] -cmdclass = { bdist_wheel = "install.CustomBuild" } +[tool.setuptools.package-data] +"*" = ["*.*"] -[tool.setuptools.packages.find] -where = ["src", "."] -exclude = ["sample*"] +[project.scripts] +download_golang_executable = "test_server_sdk.install:main_downloader_function" diff --git a/sdks/python/sample/test-data/recordings/396bab503f4b90ad88858bb4467d59b9e827dcd82e33f41c230e1b8fb43bdc8c.json b/sdks/python/sample/test-data/recordings/396bab503f4b90ad88858bb4467d59b9e827dcd82e33f41c230e1b8fb43bdc8c.json index 544cff9..ab01241 100644 --- a/sdks/python/sample/test-data/recordings/396bab503f4b90ad88858bb4467d59b9e827dcd82e33f41c230e1b8fb43bdc8c.json +++ b/sdks/python/sample/test-data/recordings/396bab503f4b90ad88858bb4467d59b9e827dcd82e33f41c230e1b8fb43bdc8c.json @@ -28,18 +28,18 @@ "Cache-Control": "max-age=0, private, must-revalidate", "Content-Encoding": "gzip", "Content-Language": "en-US", - "Content-Security-Policy": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com github.githubassets.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com www.youtube-nocookie.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com/ user-images.githubusercontent.com/ private-user-images.githubusercontent.com opengraph.githubassets.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com images.ctfassets.net/8aevphvgewt8/; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com assets.ctfassets.net/8aevphvgewt8/ videos.ctfassets.net/8aevphvgewt8/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/", + "Content-Security-Policy": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com github.githubassets.com edge.fullstory.com rs.fullstory.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com www.youtube-nocookie.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com/ user-images.githubusercontent.com/ private-user-images.githubusercontent.com opengraph.githubassets.com marketplace-screenshots.githubusercontent.com/ copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com images.ctfassets.net/8aevphvgewt8/; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com assets.ctfassets.net/8aevphvgewt8/ videos.ctfassets.net/8aevphvgewt8/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/", "Content-Type": "text/html; charset=utf-8", - "Date": "Thu, 28 Aug 2025 16:27:20 GMT", - "Etag": "W/\"386abcfaf4ff44a2cbfe6a90d3b1d6bf\"", + "Date": "Tue, 09 Sep 2025 15:53:45 GMT", + "Etag": "W/\"8bab6ba1a0068ba77443041a70a012fc\"", "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Server": "github.com", - "Set-Cookie": "_gh_sess=LU54Wf8u3%2B7e1lbnM9LXqhwCyguiOMnGz%2FNGGANwWRg6UkobMXf3bqzDnwD64hIz5SAYSEM3L7IW%2FnzMF6AocWUE6uTxfE93SbuD8kvB1sbsBJlG3DKsg9eZicTdzOZihy3aCv1Y7syr0ZSlp3eKncPgpTAYlwoQGkTDoL1ly84KK2%2F9uGLbKTT2Q1dzWO7KAuP8znAQ98y%2FifLijPjexup%2BWvn95A4SCywsc3XcDgHYP%2BFh6na7uM1pO5toR9li4h2DpBlF5vne2m%2B0jDC0Mg%3D%3D--mDFUX5IeIv53eGZl--dmN4h2k8jrFI5%2Be%2FFUNaYw%3D%3D; Path=/; HttpOnly; Secure; SameSite=Lax, _octo=GH1.1.778961946.1756398441; Path=/; Domain=github.com; Expires=Fri, 28 Aug 2026 16:27:21 GMT; Secure; SameSite=Lax, logged_in=no; Path=/; Domain=github.com; Expires=Fri, 28 Aug 2026 16:27:21 GMT; HttpOnly; Secure; SameSite=Lax", + "Set-Cookie": "_gh_sess=jjLJMJHJa3yHLlB%2FstksHC7tyxHlTIGMZnISFDY5YvN2pZ40k9erSjDcL6N4fwiK3tI4gqB3ZMiFacRZPexRYU%2Fa4QKkFSyDN1oRGfv19gkqDJWH0UygUOc9239ontofFzh8rPEEnLdtPR3Jxq1Mb2sqs8e9MTvgTueqp1XRNElJvU3h0%2BocyQ%2Fr8XI4pIdLm64jp34ysCaisN%2FFXBa9SHzEIpGfQlrvihtWYcWP%2Bmzl0S5MAwox3%2BVFOiUPdf2AnD86OMXJekNHcxgzc41SPA%3D%3D--bex5KbZP2AtUtYXh--rcQmgTr4hiau78tUK896nQ%3D%3D; Path=/; HttpOnly; Secure; SameSite=Lax, _octo=GH1.1.1430909772.1757433231; Path=/; Domain=github.com; Expires=Wed, 09 Sep 2026 15:53:51 GMT; Secure; SameSite=Lax, logged_in=no; Path=/; Domain=github.com; Expires=Wed, 09 Sep 2026 15:53:51 GMT; HttpOnly; Secure; SameSite=Lax", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "Vary": "X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With, Accept-Language,Accept-Encoding, Accept, X-Requested-With", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "deny", - "X-Github-Request-Id": "C832:288B0E:2DB350:2E5DA4:68B08369", + "X-Github-Request-Id": "D811:22D066:299B1F:2A4602:68C04D8F", "X-Xss-Protection": "0" } } diff --git a/sdks/python/checksums.json b/sdks/python/src/test_server_sdk/checksums.json similarity index 100% rename from sdks/python/checksums.json rename to sdks/python/src/test_server_sdk/checksums.json diff --git a/sdks/python/install.py b/sdks/python/src/test_server_sdk/install.py similarity index 71% rename from sdks/python/install.py rename to sdks/python/src/test_server_sdk/install.py index d58205c..46745ec 100644 --- a/sdks/python/install.py +++ b/sdks/python/src/test_server_sdk/install.py @@ -22,21 +22,22 @@ import json from pathlib import Path import requests -from setuptools.command.bdist_wheel import bdist_wheel - +import subprocess # --- Configuration --- TEST_SERVER_VERSION = "v0.2.7" GITHUB_OWNER = "google" GITHUB_REPO = "test-server" PROJECT_NAME = "test-server" +PROJECT_ROOT = Path(__file__).parent + +CHECKSUMS_PATH = PROJECT_ROOT / "checksums.json" -CHECKSUMS_PATH = Path(__file__).parent / "checksums.json" try: with open(CHECKSUMS_PATH, "r") as f: ALL_EXPECTED_CHECKSUMS = json.load(f) except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Error loading checksums.json: {e}") + print(f"Error loading checksums.json: {e}", file=sys.stderr) sys.exit(1) @@ -101,7 +102,7 @@ def download_and_verify(download_url, archive_path, version, archive_name): except Exception as e: if archive_path.exists(): archive_path.unlink() - print(f"Failed during download or verification: {e}") + print(f"Failed during download or verification: {e}", file=sys.stderr) raise @@ -129,6 +130,34 @@ def ensure_binary_is_executable(binary_path, go_os): os.chmod(binary_path, st.st_mode | stat.S_IEXEC) print(f"Set executable permission for {binary_path}") +def verify_binary_usability(binary_path: Path) -> None: + """ + Verifies the binary can be executed by running a simple command. + This helps catch corrupted downloads or architecture mismatches. + """ + print(f"Verifying usability of binary at {binary_path}...") + try: + # Running with "--help" is a safe, standard way to check execution + subprocess.run( + [str(binary_path), "--help"], + capture_output=True, + text=True, + check=True, # This will raise CalledProcessError on non-zero exit codes + timeout=10 + ) + print("Binary is executable and responding correctly.") + except (subprocess.CalledProcessError, FileNotFoundError, PermissionError, subprocess.TimeoutExpired) as e: + print(f"ERROR: The binary at {binary_path} is not usable. Error: {e}", file=sys.stderr) + # Clean up the bad binary + if binary_path.exists(): + binary_path.unlink() + raise RuntimeError( + f"The downloaded binary at {binary_path} could not be executed. " + "This can mean a corrupted download or an incorrect binary for your OS/architecture. " + "The invalid binary has been removed. Please try running the installation again. " + "Run: download_golang_executable" + ) from e + def install_binary(bin_dir: Path): """Main function to orchestrate the installation to a specific directory.""" @@ -150,22 +179,26 @@ def install_binary(bin_dir: Path): download_and_verify(download_url, archive_path, version, archive_name) extract_archive(archive_path, archive_extension, bin_dir) ensure_binary_is_executable(binary_path, go_os) - print(f"{PROJECT_NAME} binary is ready at {binary_path}") + verify_binary_usability(binary_path) + print(f"\n{PROJECT_NAME} binary is ready at {binary_path}") except Exception as e: - print(f"An error occurred during binary installation: {e}") - sys.exit(1) + print(f"\nAn error occurred during binary installation: {e}", file=sys.stderr) + # Re-raise the exception to be caught by the entry point function + raise -# --- The Setuptools Hook --- -class CustomBuild(bdist_wheel): - """Custom build command to download the binary into the correct build location.""" - def run(self): - print("--- Executing CustomBuild hook to download binary! ---") - build_py = self.get_finalized_command('build_py') - build_dir = Path(build_py.build_lib) - bin_path = build_dir / 'test_server_sdk' / 'bin' +def main_downloader_function(): + """ + Entry point that determines the install location and calls the installation logic. + """ + install_location = PROJECT_ROOT / "bin" + + try: + install_binary(install_location) + except Exception: + sys.exit(1) - install_binary(bin_path) - super().run() +if __name__ == "__main__": + main_downloader_function()