| // 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 |
| // |
| // http://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. |
| |
| package wrapper |
| |
| import ( |
| "bytes" |
| "context" |
| "os" |
| "syscall" |
| "testing" |
| "time" |
| |
| _ "github.com/bazelbuild/rsclient/internal/pkg/testsetup" |
| ) |
| |
| // TestRunSuccess verifies that a simple command runs successfully and its output is captured. |
| func TestRunSuccess(t *testing.T) { |
| var stdout bytes.Buffer |
| inv := Invocation{ |
| Command: []string{"echo", "hello"}, |
| SignalPolicy: PolicyRelay, |
| Stdout: &stdout, |
| BaseEnv: []string{}, |
| } |
| exitCode := inv.Run(context.Background()) |
| if exitCode != 0 { |
| t.Errorf("expected exit code 0, got %d", exitCode) |
| } |
| if got := stdout.String(); got != "hello\n" { |
| t.Errorf("expected stdout \"hello\\n\", got %q", got) |
| } |
| } |
| |
| // TestRunFailure verifies that a failing command returns a non-zero exit code. |
| func TestRunFailure(t *testing.T) { |
| inv := Invocation{ |
| Command: []string{"false"}, |
| SignalPolicy: PolicyRelay, |
| BaseEnv: []string{}, |
| } |
| exitCode := inv.Run(context.Background()) |
| if exitCode != 1 { |
| t.Errorf("expected exit code 1, got %d", exitCode) |
| } |
| } |
| |
| // TestRunSignalForwardingRelay verifies that signals sent to the Sigs channel |
| // are correctly forwarded to the child process in PolicyRelay mode. |
| func TestRunSignalForwardingRelay(t *testing.T) { |
| // 'sleep' will wait for a signal. |
| // We'll send it a SIGTERM. |
| sigs := make(chan os.Signal, 1) |
| inv := Invocation{ |
| Command: []string{"sleep", "10"}, |
| SignalPolicy: PolicyRelay, |
| Sigs: sigs, |
| BaseEnv: []string{}, |
| } |
| |
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) |
| defer cancel() |
| |
| done := make(chan int) |
| go func() { |
| done <- inv.Run(ctx) |
| }() |
| |
| // Give it a moment to start. |
| time.Sleep(100 * time.Millisecond) |
| sigs <- syscall.SIGTERM |
| |
| select { |
| case exitCode := <-done: |
| // When a process is terminated by a signal, Wait() returns an error, |
| // and our Run implementation returns -1. |
| if exitCode != -1 { |
| t.Errorf("expected exit code -1 after signal termination, got %d", exitCode) |
| } |
| case <-ctx.Done(): |
| t.Fatal("timed out waiting for command to exit after signal") |
| } |
| } |
| |
| // TestPopulateSystemDefaults verifies that the helper function correctly |
| // fills missing fields with standard system values. |
| func TestPopulateSystemDefaults(t *testing.T) { |
| inv := Invocation{} |
| inv.PopulateSystemDefaults() |
| |
| if inv.Stdout != os.Stdout { |
| t.Errorf("expected Stdout to be os.Stdout") |
| } |
| if inv.Stderr != os.Stderr { |
| t.Errorf("expected Stderr to be os.Stderr") |
| } |
| if inv.SignalPolicy != PolicyRelay { |
| t.Errorf("expected SignalPolicy to be %q, got %q", PolicyRelay, inv.SignalPolicy) |
| } |
| if inv.BaseEnv == nil { |
| t.Errorf("expected BaseEnv to be populated") |
| } |
| } |
| |
| type customSignal struct{} |
| |
| func (s customSignal) String() string { return "custom" } |
| func (s customSignal) Signal() {} |
| |
| // TestRun_NonSyscallSignal verifies that receiving a non-syscall signal |
| // does not cause a panic and is simply ignored for forwarding. |
| func TestRun_NonSyscallSignal(t *testing.T) { |
| sigs := make(chan os.Signal, 1) |
| inv := Invocation{ |
| Command: []string{"echo", "hi"}, |
| SignalPolicy: PolicyRelay, |
| Sigs: sigs, |
| BaseEnv: []string{}, |
| } |
| |
| // Send a signal that does not implement syscall.Signal. |
| sigs <- customSignal{} |
| |
| // Run should finish successfully without panicking. |
| exitCode := inv.Run(context.Background()) |
| if exitCode != 0 { |
| t.Errorf("expected exit code 0, got %d", exitCode) |
| } |
| } |