1// SPDX-License-Identifier: MIT
2
3package guest
4
5import (
6 "context"
7 "net"
8 "testing"
9
10 "bnema.dev/ermite-runner/internal/execstream"
11 "bnema.dev/ermite-runner/internal/execstream/execpb"
12)
13
14// The resolver of the executor and the Resolve handler of the host meet on the
15// stream: requests are answered by id, in any order, and an error of the host
16// reaches the caller.
17func TestResolverRoundTrip(t *testing.T) {
18 ln, err := net.Listen("tcp", "127.0.0.1:0")
19 if err != nil {
20 t.Skip(err)
21 }
22 defer func() { _ = ln.Close() }()
23 guestSide, err := net.Dial("tcp", ln.Addr().String())
24 if err != nil {
25 t.Fatal(err)
26 }
27 hostSide, err := ln.Accept()
28 if err != nil {
29 t.Fatal(err)
30 }
31 defer func() { _ = hostSide.Close() }()
32 defer func() { _ = guestSide.Close() }()
33
34 gconn := execstream.NewConn(guestSide)
35 res := newResolver(gconn)
36 go func() { // the guest's frame reader
37 for {
38 f, err := gconn.Recv()
39 if err != nil {
40 return
41 }
42 if r := f.GetResolveResponse(); r != nil {
43 res.deliver(r)
44 }
45 }
46 }()
47 // The executor says Hello first, as it does at startup.
48 if err := gconn.Send(&execpb.Frame{Body: &execpb.Frame_Hello{Hello: &execpb.Hello{}}}); err != nil {
49 t.Fatal(err)
50 }
51 done := make(chan struct{})
52 go func() {
53 defer close(done)
54 _, _, _ = execstream.DriveJob(hostSide, &execpb.Run{}, nil, execstream.JobHandler{
55 Resolve: func(_ context.Context, r *execpb.ResolveRequest) *execpb.ResolveResponse {
56 if len(r.Uses) == 1 && r.Uses[0] == "bad@v1" {
57 return &execpb.ResolveResponse{Error: "no"}
58 }
59 out := &execpb.ResolveResponse{}
60 for _, u := range r.Uses {
61 out.Actions = append(out.Actions, &execpb.ResolvedAction{Ref: u, Token: "t-" + u, ResolvedSha: "sha"})
62 }
63 return out
64 },
65 })
66 }()
67 // DriveJob sends Run after Hello; the guest ignores it.
68 got, err := res.resolve(context.Background(), []string{"a/b@v1", "c/d@v2"})
69 if err != nil || len(got) != 2 || got[1].Ref != "c/d@v2" || got[1].Token != "t-c/d@v2" || got[0].ResolvedSHA != "sha" {
70 t.Fatalf("%+v %v", got, err)
71 }
72 if _, err := res.resolve(context.Background(), []string{"bad@v1"}); err == nil || err.Error() != "no" {
73 t.Fatalf("error not passed: %v", err)
74 }
75 ctx, cancel := context.WithCancel(context.Background())
76 cancel()
77 _ = guestSide.Close()
78 if _, err := res.resolve(ctx, []string{"x/y@z"}); err == nil {
79 t.Error("a closed stream must fail")
80 }
81 <-done
82}