Skip to content
Open
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
6 changes: 1 addition & 5 deletions gapic-common/.toys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@
t.fail_on_undocumented_objects = false # TODO: Fix so this can be enabled
t.bundler = true
end
alias_tool :yard, :yardoc

expand :gem_build

expand :gem_build, name: "install", install_gem: true
tool "yard", delegate_to: "yardoc"

tool "ci" do
include :exec, e: true
Expand Down
139 changes: 139 additions & 0 deletions gapic-common/.toys/test-integration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# frozen_string_literal: true

# Copyright 2026 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.

require "socket"
require "tmpdir"

expand :minitest, name: "" do |t|
t.libs = ["lib", "integration"]
t.files = ["integration/**/*_test.rb"]
t.bundler = true
end

alias_method :run_minitest, :run

def run
if !ENV["SHOWCASE_ENDPOINT"].to_s.empty?
run_minitest
return
end

bin = resolve_showcase_bin
if bin.nil?
if !ENV["CI"].to_s.empty?
logger.error "No SHOWCASE_ENDPOINT or gapic-showcase binary found in CI environment."
exit 1
else
logger.warn "Skipping integration tests: no SHOWCASE_ENDPOINT or gapic-showcase binary found."
return
end
end

verify_showcase_version! bin

port = allocate_port
fallback_port = allocate_port
log_path = File.join Dir.tmpdir, "gapic-showcase-#{Process.pid}-#{Time.now.to_i}.log"

pid = Process.spawn(
bin, "run",
"--port", ":#{port}",
"--fallback-port", ":#{fallback_port}",
out: log_path,
err: log_path,
pgroup: true
)

begin
wait_for_showcase! pid, port, log_path
ENV["SHOWCASE_ENDPOINT"] = "http://localhost:#{port}"
run_minitest
ensure
if pid
begin
Process.kill "-TERM", pid
Process.waitpid pid
rescue Errno::ESRCH, Errno::ECHILD
# Process already terminated
end
end
end
end

def resolve_showcase_bin
env_bin = ENV["SHOWCASE_BIN"].to_s
return env_bin unless env_bin.empty?

ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
candidate = File.join dir, "gapic-showcase"
return candidate if File.executable?(candidate) && !File.directory?(candidate)
end
nil
end

def verify_showcase_version!(bin)
output = begin
IO.popen([bin, "--version"], err: [:child, :out], &:read).strip
rescue StandardError => e
logger.error "Failed to execute '#{bin} --version': #{e.message}"
exit 1
end

version_match = output[/\d+\.\d+(?:\.\d+)*/]
if version_match.nil?
logger.error "Could not parse version from '#{bin} --version' output: #{output.inspect}"
exit 1
end

if Gem::Version.new(version_match) < Gem::Version.new("0.43")
logger.error "gapic-showcase version #{version_match} is too old (minimum required is 0.43)."
exit 1
end
end

def allocate_port
server = TCPServer.open "127.0.0.1", 0
port = server.addr[1]
server.close
port
end

def wait_for_showcase!(pid, port, log_path)
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 10.0

loop do
exited_pid, status = Process.waitpid2 pid, Process::WNOHANG
if exited_pid
logger.error "gapic-showcase exited prematurely (status: #{status.exitstatus}). Log file: #{log_path}"
exit 1
end

begin
sock = TCPSocket.new "127.0.0.1", port
sock.close
return
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
# Server not ready yet
end

if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
logger.error "Timed out waiting 10s for gapic-showcase to listen on port #{port}. Log file: #{log_path}"
exit 1
end

sleep 0.1
end
end
687 changes: 687 additions & 0 deletions gapic-common/design/resumable_upload/implementation-guide.md

Large diffs are not rendered by default.

286 changes: 286 additions & 0 deletions gapic-common/design/resumable_upload/integration-test-plan.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions gapic-common/integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Integration Tests

This directory contains integration tests for `gapic-common`, executed against a running `gapic-showcase` server via the `toys test-integration` command.

## Running Integration Tests

```bash
toys test-integration
```

You can pass standard Minitest flags to filter or seed test runs:

```bash
toys test-integration --name /resumable_upload/ --seed 1234
```

## Showcase Server Management & Lifecycle

The `toys test-integration` command (`.toys/test-integration.rb`) manages the `gapic-showcase` server lifecycle automatically:

1. **Existing Endpoint (`SHOWCASE_ENDPOINT`)**:
- If `ENV["SHOWCASE_ENDPOINT"]` is present and non-empty, `toys test-integration` skips binary resolution and runs the Minitest suite directly against that endpoint.

2. **Binary Resolution (`SHOWCASE_BIN` / `PATH`)**:
- When `SHOWCASE_ENDPOINT` is not set, the runner checks `ENV["SHOWCASE_BIN"]` first, then searches `ENV["PATH"]` for an executable `gapic-showcase` binary.
- **Version Check**: The runner executes `<binary> --version` and verifies that the version is at least `0.43`. If the version is older than `0.43`, it logs an error and exits with status `1`.

3. **Missing Endpoint and Binary**:
- **CI Environment (`ENV["CI"]` set)**: Fails immediately with exit status `1`.
- **Local Environment (`ENV["CI"]` unset)**: Logs an informational message and skips integration tests cleanly (exit status `0`).

4. **Ephemeral Port Allocation & Polling**:
- Allocates two ephemeral TCP ports on `127.0.0.1` for `--port :<port>` and `--fallback-port :<fallback_port>` to avoid port collisions across concurrent runs.
- Spawns `gapic-showcase run --port :<port> --fallback-port :<fallback_port>` in a dedicated process group (`pgroup: true`) with `stdout` and `stderr` redirected to a temporary log file in `Dir.tmpdir`.
- Polls `127.0.0.1:<port>` with a 10-second monotonic clock budget while checking `Process.waitpid2` (`WNOHANG`) on each iteration. If the process exits prematurely or fails to accept TCP connections within 10 seconds, the runner prints the path to the log file and exits with status `1`.

5. **Execution & Teardown**:
- Sets `ENV["SHOWCASE_ENDPOINT"] = "http://localhost:#{port}"` and runs the Minitest suite (`integration/**/*_test.rb`).
- An `ensure` block sends `SIGTERM` to the entire process group (`-TERM`) and reaps the child process so no background showcase processes are leaked.

## Logging and Diagnostics

Each integration test captures `DEBUG`-level client and driver logs into an in-memory buffer during execution:

- **Automatic Failure Dump**: If a test fails or raises an unhandled exception, the captured trace is automatically dumped to `stderr` during `teardown`.
- **Force Log Dump (`SHOWCASE_LOG`)**: Set `SHOWCASE_LOG=1` (or any non-empty value) to dump the captured trace for all executed tests, including passing ones:

```bash
SHOWCASE_LOG=1 toys test-integration
```

Loading
Loading