init
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"browser.local/run/protocol"
|
||||
)
|
||||
|
||||
const dependencyTestDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
type dependencyTestSupervisor struct {
|
||||
mu sync.Mutex
|
||||
calls [][]string
|
||||
present bool
|
||||
block bool
|
||||
}
|
||||
|
||||
func (supervisor *dependencyTestSupervisor) Run(ctx context.Context, command ProcessCommand) (ProcessResult, error) {
|
||||
supervisor.mu.Lock()
|
||||
supervisor.calls = append(supervisor.calls, append([]string(nil), command.Args...))
|
||||
block := supervisor.block
|
||||
present := supervisor.present
|
||||
if len(command.Args) > 0 && command.Args[0] == "apt-get" {
|
||||
supervisor.present = true
|
||||
present = true
|
||||
}
|
||||
supervisor.mu.Unlock()
|
||||
if block {
|
||||
<-ctx.Done()
|
||||
return ProcessResult{ExitCode: -1}, ctx.Err()
|
||||
}
|
||||
if len(command.Args) > 0 && command.Args[0] == "java" {
|
||||
if !present {
|
||||
return ProcessResult{ExitCode: 1}, errors.New("not installed")
|
||||
}
|
||||
return ProcessResult{ExitCode: 0, Stderr: "openjdk version 21.0.2"}, nil
|
||||
}
|
||||
return ProcessResult{ExitCode: 0}, nil
|
||||
}
|
||||
|
||||
func (supervisor *dependencyTestSupervisor) count(name string) int {
|
||||
supervisor.mu.Lock()
|
||||
defer supervisor.mu.Unlock()
|
||||
count := 0
|
||||
for _, call := range supervisor.calls {
|
||||
if len(call) > 0 && call[0] == name {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
type dependencyTestDownloader struct {
|
||||
payload []byte
|
||||
sourceURL string
|
||||
destination string
|
||||
}
|
||||
|
||||
func (downloader *dependencyTestDownloader) Download(_ context.Context, sourceURL, destination string, _ int64) (int64, string, error) {
|
||||
downloader.sourceURL = sourceURL
|
||||
downloader.destination = destination
|
||||
if err := os.MkdirAll(filepath.Dir(destination), 0o700); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
if err := os.WriteFile(destination, downloader.payload, 0o600); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return int64(len(downloader.payload)), bytesChecksum(downloader.payload), nil
|
||||
}
|
||||
|
||||
func TestDependencyInstallExecutesTypedPlanAndResumesCompletedSteps(t *testing.T) {
|
||||
if !dependencyVersionAtLeast("openjdk version 21.0.2", "21") {
|
||||
t.Fatal("version comparator rejected valid Java version")
|
||||
}
|
||||
client := newFakeWorkerClient()
|
||||
assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall)
|
||||
client.dependencyInput = dependencyInputForAssignment(assignment)
|
||||
runner := &dependencyTestSupervisor{}
|
||||
worker, err := NewWorker(workerTestConfig(t), client, WithDependencyCommandRunner(runner))
|
||||
if err != nil {
|
||||
t.Fatalf("new worker: %v", err)
|
||||
}
|
||||
worker.state.SessionToken = "session-token"
|
||||
|
||||
first := worker.executeDependencyJob(context.Background(), assignment)
|
||||
if first.State != lifecycleResultStateSucceeded || first.ExecutionResult.Kind != "dependency.install" || first.ExecutionResult.Checksum != dependencyTestDigest {
|
||||
state, evidence, probeErr := worker.executor.runDependencyProbe(context.Background(), client.dependencyInput.Probe, client.dependencyInput.Bindings)
|
||||
t.Fatalf("expected real typed dependency install, got %+v calls=%+v present=%v probe=%s evidence=%s err=%v", first, runner.calls, runner.present, state, evidence, probeErr)
|
||||
}
|
||||
second := worker.executeDependencyJob(context.Background(), assignment)
|
||||
if second.State != lifecycleResultStateSucceeded {
|
||||
t.Fatalf("expected journal resume success, got %+v", second)
|
||||
}
|
||||
if runner.count("apt-get") != 1 {
|
||||
t.Fatalf("completed package step must not repeat, calls=%+v", runner.calls)
|
||||
}
|
||||
if !strings.Contains(first.ExecutionResult.Content, `"completedSteps":1`) || strings.Contains(first.ExecutionResult.Content, "/Users/") {
|
||||
t.Fatalf("dependency evidence is not safe: %s", first.ExecutionResult.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyProbeAndInstallRejectUnsafeOrCancelledWork(t *testing.T) {
|
||||
client := newFakeWorkerClient()
|
||||
assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall)
|
||||
input := dependencyInputForAssignment(assignment)
|
||||
input.Plan.Steps[0].PackageName = "openjdk;rm"
|
||||
client.dependencyInput = input
|
||||
worker, err := NewWorker(workerTestConfig(t), client, WithDependencyCommandRunner(&dependencyTestSupervisor{}))
|
||||
if err != nil {
|
||||
t.Fatalf("new worker: %v", err)
|
||||
}
|
||||
worker.state.SessionToken = "session-token"
|
||||
unsafe := worker.executeDependencyJob(context.Background(), assignment)
|
||||
if unsafe.State != lifecycleResultStateFailed || unsafe.ErrorCode != "dependency_install_failed" {
|
||||
t.Fatalf("expected unsafe package rejection, got %+v", unsafe)
|
||||
}
|
||||
|
||||
check := dependencyAssignment(protocol.RunCapabilityDependenciesCheck)
|
||||
checkInput := dependencyInputForAssignment(check)
|
||||
checkInput.Plan = protocol.DependencyInstallPlan{}
|
||||
client.dependencyInput = checkInput
|
||||
blocking := &dependencyTestSupervisor{block: true}
|
||||
worker.executor.dependencyRunner = blocking
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||||
defer cancel()
|
||||
cancelled := worker.executeDependencyJob(ctx, check)
|
||||
if cancelled.State != lifecycleResultStateCancelled || cancelled.ErrorCode != "dependency_probe_cancelled" {
|
||||
t.Fatalf("expected cancelled dependency probe, got %+v", cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedDependencyDownloadUsesHTTPSChecksumAndScopedDestination(t *testing.T) {
|
||||
payload := []byte("verified dependency")
|
||||
downloader := &dependencyTestDownloader{payload: payload}
|
||||
executor := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(t.TempDir()), WithDependencyDownloader(downloader))
|
||||
assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall)
|
||||
input := dependencyInputForAssignment(assignment)
|
||||
step := protocol.DependencyInstallStep{Type: "verified-download", TargetKey: "tools/java", DownloadRef: "https://downloads.example.test/java", Checksum: bytesChecksum(payload)}
|
||||
if err := executor.runDependencyInstallStep(context.Background(), assignment, input, step, 0); err != nil {
|
||||
t.Fatalf("verified download: %v", err)
|
||||
}
|
||||
if downloader.sourceURL != step.DownloadRef || !strings.Contains(downloader.destination, "dependency-files") || strings.Contains(downloader.destination, "..") {
|
||||
t.Fatalf("unexpected scoped download: source=%s destination=%s", downloader.sourceURL, downloader.destination)
|
||||
}
|
||||
if _, err := validateDependencyDownloadURL("https://127.0.0.1/tool"); err == nil {
|
||||
t.Fatal("expected private dependency download host rejection")
|
||||
}
|
||||
}
|
||||
|
||||
func dependencyAssignment(capability string) protocol.RunJobAssignment {
|
||||
assignment := workerJobAssignment(capability)
|
||||
assignment.LeaseToken = "lease-dependency"
|
||||
assignment.Attempt = 1
|
||||
assignment.State = "running"
|
||||
if capability == protocol.RunCapabilityDependenciesInstall {
|
||||
assignment.TargetKey = "dependencies/install/install-java"
|
||||
} else {
|
||||
assignment.TargetKey = "dependencies/java-runtime"
|
||||
}
|
||||
return assignment
|
||||
}
|
||||
|
||||
func dependencyInputForAssignment(assignment protocol.RunJobAssignment) protocol.DependencyExecutionInputResponse {
|
||||
return protocol.DependencyExecutionInputResponse{
|
||||
JobID: assignment.JobID,
|
||||
ServerInstanceID: assignment.ServerInstanceID,
|
||||
RunEndpointID: assignment.RunEndpointID,
|
||||
PluginID: "game.minecraft",
|
||||
PluginVersion: "1.0.0",
|
||||
ProfileKey: "local",
|
||||
TargetOS: runtime.GOOS,
|
||||
TargetArch: runtime.GOARCH,
|
||||
PlanDigest: dependencyTestDigest,
|
||||
Probe: protocol.DependencyProbe{Key: "java-runtime", Kind: "java.version", TargetKey: "java", MinimumVersion: "21", Platforms: []string{runtime.GOOS}},
|
||||
Plan: protocol.DependencyInstallPlan{Key: "install-java", Title: "Install Java", Platforms: []string{runtime.GOOS}, Steps: []protocol.DependencyInstallStep{{Type: "package", TargetKey: "java", PackageManager: "apt", PackageName: "openjdk-21-jre"}}},
|
||||
Bindings: map[string]string{"java": "java"},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user