package nixery import ( "context" "errors" "io" "testing" "time" "tangled.org/core/spindle/config" "tangled.org/core/spindle/models" ) func TestNewDefersDockerClientUntilWorkflowSetup(t *testing.T) { t.Setenv("DOCKER_HOST", "tcp://127.0.0.1:2376") t.Setenv("DOCKER_TLS_VERIFY", "1") t.Setenv("DOCKER_CERT_PATH", t.TempDir()) e, err := New(context.Background(), &config.Config{}) if err != nil { t.Fatal(err) } if e.docker != nil { t.Fatal("docker client initialized during engine initialization") } if _, err := e.ensureDocker(); err == nil { t.Fatal("expected incomplete Docker TLS configuration to fail when first used") } } func TestDestroyWorkflowCleansUpAfterCancellation(t *testing.T) { e, err := New(context.Background(), &config.Config{}) if err != nil { t.Fatal(err) } wid := models.WorkflowId{PipelineId: "pipeline", Name: "build.yml"} cleanedUp := make(chan error, 1) e.registerCleanup(wid, func(ctx context.Context) error { cleanedUp <- ctx.Err() return nil }) canceled, cancel := context.WithCancel(context.Background()) cancel() if err := e.DestroyWorkflow(canceled, wid); err != nil { t.Fatal(err) } select { case err := <-cleanedUp: if err != nil { t.Fatalf("cleanup ran with a dead context: %v", err) } case <-time.After(5 * time.Second): t.Fatal("cleanup did not run") } } // only ends when closed, like a hijacked exec attach nothing will write to type hangingStream struct{ closed chan struct{} } func newHangingStream() *hangingStream { return &hangingStream{closed: make(chan struct{})} } func (s *hangingStream) Read([]byte) (int, error) { <-s.closed return 0, io.EOF } func (s *hangingStream) close() { select { case <-s.closed: default: close(s.closed) } } type failingStream struct{ err error } func (s failingStream) Read([]byte) (int, error) { return 0, s.err } func TestAwaitExecUnblocksWhenTheWorkflowContextEnds(t *testing.T) { stream := newHangingStream() ctx, cancel := context.WithCancel(context.Background()) cancel() done := make(chan error, 1) go func() { done <- awaitExec(ctx, stream, stream.close) }() select { case err := <-done: if !errors.Is(err, context.Canceled) { t.Fatalf("awaitExec() = %v, want context.Canceled", err) } case <-time.After(5 * time.Second): t.Fatal("a cancelled workflow is still waiting on the exec stream") } } func TestAwaitExecReturnsWhenTheStreamEnds(t *testing.T) { stream := newHangingStream() done := make(chan error, 1) go func() { done <- awaitExec(context.Background(), stream, stream.close) }() stream.close() select { case err := <-done: if err != nil { t.Fatalf("awaitExec() = %v, want nil", err) } case <-time.After(5 * time.Second): t.Fatal("awaitExec did not return after the stream ended") } } func TestAwaitExecReturnsStreamErrors(t *testing.T) { broken := errors.New("stream broke") if err := awaitExec(context.Background(), failingStream{err: broken}, func() {}); !errors.Is(err, broken) { t.Fatalf("awaitExec() = %v, want %v", err, broken) } }