Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
2 changes: 0 additions & 2 deletions sdks/python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
include LICENSE
include README.md
include install.py
include checksums.json

prune sample
23 changes: 12 additions & 11 deletions sdks/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand All @@ -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
Expand All @@ -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
```

Expand Down
9 changes: 4 additions & 5 deletions sdks/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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."""
Expand All @@ -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()
Loading