test(app): use generated transport mocks
- commit
- bc9ddfa06f28e25f56305610422bfbc1b2a67d49
- author
- brice · · 2026-07-15 07:02 UTC
- parent
- 3dc8ca0
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +53 -78
M internal/app/run_test.go +53 -78
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | "os/exec" | |
| 11 | 11 | "path/filepath" | |
| 12 | 12 | "strings" | |
| 13 | - | "sync" | |
| 14 | 13 | "testing" | |
| 15 | 14 | "time" | |
| 16 | 15 | ||
| @@ -25,20 +24,23 @@ import ( | |||
| 25 | 24 | "github.com/stretchr/testify/require" | |
| 26 | 25 | ) | |
| 27 | 26 | ||
| 28 | - | type firstHelloTestTransport struct { | |
| 29 | - | frames []ports.Frame | |
| 30 | - | } | |
| 31 | - | ||
| 32 | - | func (t *firstHelloTestTransport) Send(ports.Frame) error { return nil } | |
| 33 | - | func (t *firstHelloTestTransport) Recv() (ports.Frame, error) { | |
| 34 | - | if len(t.frames) == 0 { | |
| 35 | - | return ports.Frame{}, io.EOF | |
| 36 | - | } | |
| 37 | - | frame := t.frames[0] | |
| 38 | - | t.frames = t.frames[1:] | |
| 39 | - | return frame, nil | |
| 27 | + | // scriptRecv makes a transport yield frames in order, then wait for done | |
| 28 | + | // before returning EOF. The shared done channel coordinates proxy readers | |
| 29 | + | // without relying on scheduling or sleeps. | |
| 30 | + | func scriptRecv(tr *portsmocks.MockTransport, done <-chan struct{}, frames ...ports.Frame) *portsmocks.MockTransport_Recv_Call { | |
| 31 | + | script := make(chan ports.Frame, len(frames)) | |
| 32 | + | for _, frame := range frames { | |
| 33 | + | script <- frame | |
| 34 | + | } | |
| 35 | + | return tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) { | |
| 36 | + | select { | |
| 37 | + | case frame := <-script: | |
| 38 | + | return frame, nil | |
| 39 | + | case <-done: | |
| 40 | + | return ports.Frame{}, io.EOF | |
| 41 | + | } | |
| 42 | + | }) | |
| 40 | 43 | } | |
| 41 | - | func (t *firstHelloTestTransport) Close() error { return nil } | |
| 42 | 44 | ||
| 43 | 45 | func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | |
| 44 | 46 | for _, tt := range []struct { | |
| @@ -63,10 +65,12 @@ func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | |||
| 63 | 65 | Env: []string{"CLIENT=value"}, | |
| 64 | 66 | } | |
| 65 | 67 | nonHello := ports.Frame{Type: ports.MsgInput, Payload: []byte("unchanged")} | |
| 66 | - | transport := &firstHelloTestTransport{frames: []ports.Frame{ | |
| 67 | - | {Type: ports.MsgHello, Payload: ports.MarshalHello(original)}, | |
| 68 | + | transport := portsmocks.NewMockTransport(t) | |
| 69 | + | done := make(chan struct{}) | |
| 70 | + | scriptRecv(transport, done, | |
| 71 | + | ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(original)}, | |
| 68 | 72 | nonHello, | |
| 69 | - | }} | |
| 73 | + | ).Times(2) | |
| 70 | 74 | wrapped := newFirstHelloTransport(transport, tt.session, []string{"REMOTE=one", "TOKEN=a=b=c"}) | |
| 71 | 75 | ||
| 72 | 76 | frame, err := wrapped.Recv() | |
| @@ -86,26 +90,24 @@ func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | |||
| 86 | 90 | ||
| 87 | 91 | func TestRunStdioProxyReplacesHelloEnvironmentAtDaemonBoundary(t *testing.T) { | |
| 88 | 92 | original := remoteProxyHello() | |
| 89 | - | client := newRemoteProxyClient(original) | |
| 90 | - | daemon := newRemoteProxyDaemon() | |
| 93 | + | client, daemon, sent := newRemoteProxyTransports(t, original) | |
| 91 | 94 | ||
| 92 | 95 | err := runStdioProxy(context.Background(), client, daemon, []string{"REMOTE=one", "TOKEN=a=b=c"}, nil) | |
| 93 | 96 | require.NoError(t, err) | |
| 94 | 97 | ||
| 95 | - | got := daemon.firstHello(t) | |
| 98 | + | got := firstHello(t, sent) | |
| 96 | 99 | original.Env = []string{"REMOTE=one", "TOKEN=a=b=c"} | |
| 97 | 100 | require.Equal(t, original, got) | |
| 98 | 101 | } | |
| 99 | 102 | ||
| 100 | 103 | func TestRunUDPProxyRuntimeReplacesHelloEnvironmentAndSessionAtDaemonBoundary(t *testing.T) { | |
| 101 | 104 | original := remoteProxyHello() | |
| 102 | - | client := newRemoteProxyClient(original) | |
| 103 | - | daemon := newRemoteProxyDaemon() | |
| 105 | + | client, daemon, sent := newRemoteProxyTransports(t, original) | |
| 104 | 106 | ||
| 105 | 107 | err := runUDPProxyRuntime(context.Background(), "remote-work", client, daemon, []string{"REMOTE=udp", "TOKEN=a=b=c"}, nil) | |
| 106 | 108 | require.NoError(t, err) | |
| 107 | 109 | ||
| 108 | - | got := daemon.firstHello(t) | |
| 110 | + | got := firstHello(t, sent) | |
| 109 | 111 | original.Name = "remote-work" | |
| 110 | 112 | original.Env = []string{"REMOTE=udp", "TOKEN=a=b=c"} | |
| 111 | 113 | require.Equal(t, original, got) | |
| @@ -126,62 +128,35 @@ func remoteProxyHello() ports.Hello { | |||
| 126 | 128 | } | |
| 127 | 129 | } | |
| 128 | 130 | ||
| 129 | - | type remoteProxyClient struct { | |
| 130 | - | first ports.Frame | |
| 131 | - | done chan struct{} | |
| 132 | - | once sync.Once | |
| 133 | - | } | |
| 134 | - | ||
| 135 | - | func newRemoteProxyClient(hello ports.Hello) *remoteProxyClient { | |
| 136 | - | return &remoteProxyClient{ | |
| 137 | - | first: ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}, | |
| 138 | - | done: make(chan struct{}), | |
| 139 | - | } | |
| 140 | - | } | |
| 141 | - | ||
| 142 | - | func (t *remoteProxyClient) Send(ports.Frame) error { return nil } | |
| 143 | - | func (t *remoteProxyClient) Recv() (ports.Frame, error) { | |
| 144 | - | if t.first.Type != 0 { | |
| 145 | - | frame := t.first | |
| 146 | - | t.first = ports.Frame{} | |
| 147 | - | return frame, nil | |
| 148 | - | } | |
| 149 | - | <-t.done | |
| 150 | - | return ports.Frame{}, io.EOF | |
| 151 | - | } | |
| 152 | - | func (t *remoteProxyClient) Close() error { | |
| 153 | - | t.once.Do(func() { close(t.done) }) | |
| 154 | - | return nil | |
| 155 | - | } | |
| 156 | - | ||
| 157 | - | type remoteProxyDaemon struct { | |
| 158 | - | sent chan ports.Frame | |
| 159 | - | done chan struct{} | |
| 160 | - | once sync.Once | |
| 161 | - | } | |
| 162 | - | ||
| 163 | - | func newRemoteProxyDaemon() *remoteProxyDaemon { | |
| 164 | - | return &remoteProxyDaemon{sent: make(chan ports.Frame, 1), done: make(chan struct{})} | |
| 165 | - | } | |
| 166 | - | ||
| 167 | - | func (t *remoteProxyDaemon) Send(frame ports.Frame) error { | |
| 168 | - | t.sent <- frame | |
| 169 | - | t.once.Do(func() { close(t.done) }) | |
| 170 | - | return nil | |
| 171 | - | } | |
| 172 | - | func (t *remoteProxyDaemon) Recv() (ports.Frame, error) { | |
| 173 | - | <-t.done | |
| 174 | - | return ports.Frame{}, io.EOF | |
| 175 | - | } | |
| 176 | - | func (t *remoteProxyDaemon) Close() error { | |
| 177 | - | t.once.Do(func() { close(t.done) }) | |
| 178 | - | return nil | |
| 179 | - | } | |
| 180 | - | func (t *remoteProxyDaemon) firstHello(testingT *testing.T) ports.Hello { | |
| 181 | - | testingT.Helper() | |
| 182 | - | frame := <-t.sent | |
| 131 | + | func newRemoteProxyTransports(t *testing.T, hello ports.Hello) (*portsmocks.MockTransport, *portsmocks.MockTransport, <-chan ports.Frame) { | |
| 132 | + | t.Helper() | |
| 133 | + | client := portsmocks.NewMockTransport(t) | |
| 134 | + | daemon := portsmocks.NewMockTransport(t) | |
| 135 | + | clientDone := make(chan struct{}) | |
| 136 | + | daemonDone := make(chan struct{}) | |
| 137 | + | sent := make(chan ports.Frame, 1) | |
| 138 | + | ||
| 139 | + | scriptRecv(client, clientDone, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}).Maybe() | |
| 140 | + | client.EXPECT().Close().RunAndReturn(func() error { | |
| 141 | + | close(clientDone) | |
| 142 | + | return nil | |
| 143 | + | }).Once() | |
| 144 | + | daemon.EXPECT().Send(mock.Anything).RunAndReturn(func(frame ports.Frame) error { | |
| 145 | + | sent <- frame | |
| 146 | + | close(daemonDone) | |
| 147 | + | return nil | |
| 148 | + | }).Once() | |
| 149 | + | scriptRecv(daemon, daemonDone).Once() | |
| 150 | + | daemon.EXPECT().Close().Return(nil).Once() | |
| 151 | + | ||
| 152 | + | return client, daemon, sent | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | func firstHello(t *testing.T, sent <-chan ports.Frame) ports.Hello { | |
| 156 | + | t.Helper() | |
| 157 | + | frame := <-sent | |
| 183 | 158 | hello, err := ports.UnmarshalHello(frame.Payload) | |
| 184 | - | require.NoError(testingT, err) | |
| 159 | + | require.NoError(t, err) | |
| 185 | 160 | return hello | |
| 186 | 161 | } | |
| 187 | 162 | ||
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | "os/exec" | 10 | "os/exec" | ||
| 11 | "path/filepath" | 11 | "path/filepath" | ||
| 12 | "strings" | 12 | "strings" | ||
| 13 | - | "sync" | |||
| 14 | "testing" | 13 | "testing" | ||
| 15 | "time" | 14 | "time" | ||
| 16 | 15 | ||||
| @@ -25,20 +24,23 @@ import ( | |||||
| 25 | "github.com/stretchr/testify/require" | 24 | "github.com/stretchr/testify/require" | ||
| 26 | ) | 25 | ) | ||
| 27 | 26 | ||||
| 28 | - | type firstHelloTestTransport struct { | 27 | + | // scriptRecv makes a transport yield frames in order, then wait for done |
| 29 | - | frames []ports.Frame | 28 | + | // before returning EOF. The shared done channel coordinates proxy readers |
| 30 | - | } | 29 | + | // without relying on scheduling or sleeps. |
| 31 | - | 30 | + | func scriptRecv(tr *portsmocks.MockTransport, done <-chan struct{}, frames ...ports.Frame) *portsmocks.MockTransport_Recv_Call { | |
| 32 | - | func (t *firstHelloTestTransport) Send(ports.Frame) error { return nil } | 31 | + | script := make(chan ports.Frame, len(frames)) |
| 33 | - | func (t *firstHelloTestTransport) Recv() (ports.Frame, error) { | 32 | + | for _, frame := range frames { |
| 34 | - | if len(t.frames) == 0 { | 33 | + | script <- frame |
| 35 | - | return ports.Frame{}, io.EOF | 34 | + | } |
| 36 | - | } | 35 | + | return tr.EXPECT().Recv().RunAndReturn(func() (ports.Frame, error) { |
| 37 | - | frame := t.frames[0] | 36 | + | select { |
| 38 | - | t.frames = t.frames[1:] | 37 | + | case frame := <-script: |
| 39 | - | return frame, nil | 38 | + | return frame, nil |
| 39 | + | case <-done: | |||
| 40 | + | return ports.Frame{}, io.EOF | |||
| 41 | + | } | |||
| 42 | + | }) | |||
| 40 | } | 43 | } | ||
| 41 | - | func (t *firstHelloTestTransport) Close() error { return nil } | |||
| 42 | 44 | ||||
| 43 | func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | 45 | func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | ||
| 44 | for _, tt := range []struct { | 46 | for _, tt := range []struct { | ||
| @@ -63,10 +65,12 @@ func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | |||||
| 63 | Env: []string{"CLIENT=value"}, | 65 | Env: []string{"CLIENT=value"}, | ||
| 64 | } | 66 | } | ||
| 65 | nonHello := ports.Frame{Type: ports.MsgInput, Payload: []byte("unchanged")} | 67 | nonHello := ports.Frame{Type: ports.MsgInput, Payload: []byte("unchanged")} | ||
| 66 | - | transport := &firstHelloTestTransport{frames: []ports.Frame{ | 68 | + | transport := portsmocks.NewMockTransport(t) |
| 67 | - | {Type: ports.MsgHello, Payload: ports.MarshalHello(original)}, | 69 | + | done := make(chan struct{}) |
| 70 | + | scriptRecv(transport, done, | |||
| 71 | + | ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(original)}, | |||
| 68 | nonHello, | 72 | nonHello, | ||
| 69 | - | }} | 73 | + | ).Times(2) |
| 70 | wrapped := newFirstHelloTransport(transport, tt.session, []string{"REMOTE=one", "TOKEN=a=b=c"}) | 74 | wrapped := newFirstHelloTransport(transport, tt.session, []string{"REMOTE=one", "TOKEN=a=b=c"}) | ||
| 71 | 75 | ||||
| 72 | frame, err := wrapped.Recv() | 76 | frame, err := wrapped.Recv() | ||
| @@ -86,26 +90,24 @@ func TestFirstHelloTransportReplacesRemoteEnvironment(t *testing.T) { | |||||
| 86 | 90 | ||||
| 87 | func TestRunStdioProxyReplacesHelloEnvironmentAtDaemonBoundary(t *testing.T) { | 91 | func TestRunStdioProxyReplacesHelloEnvironmentAtDaemonBoundary(t *testing.T) { | ||
| 88 | original := remoteProxyHello() | 92 | original := remoteProxyHello() | ||
| 89 | - | client := newRemoteProxyClient(original) | 93 | + | client, daemon, sent := newRemoteProxyTransports(t, original) |
| 90 | - | daemon := newRemoteProxyDaemon() | |||
| 91 | 94 | ||||
| 92 | err := runStdioProxy(context.Background(), client, daemon, []string{"REMOTE=one", "TOKEN=a=b=c"}, nil) | 95 | err := runStdioProxy(context.Background(), client, daemon, []string{"REMOTE=one", "TOKEN=a=b=c"}, nil) | ||
| 93 | require.NoError(t, err) | 96 | require.NoError(t, err) | ||
| 94 | 97 | ||||
| 95 | - | got := daemon.firstHello(t) | 98 | + | got := firstHello(t, sent) |
| 96 | original.Env = []string{"REMOTE=one", "TOKEN=a=b=c"} | 99 | original.Env = []string{"REMOTE=one", "TOKEN=a=b=c"} | ||
| 97 | require.Equal(t, original, got) | 100 | require.Equal(t, original, got) | ||
| 98 | } | 101 | } | ||
| 99 | 102 | ||||
| 100 | func TestRunUDPProxyRuntimeReplacesHelloEnvironmentAndSessionAtDaemonBoundary(t *testing.T) { | 103 | func TestRunUDPProxyRuntimeReplacesHelloEnvironmentAndSessionAtDaemonBoundary(t *testing.T) { | ||
| 101 | original := remoteProxyHello() | 104 | original := remoteProxyHello() | ||
| 102 | - | client := newRemoteProxyClient(original) | 105 | + | client, daemon, sent := newRemoteProxyTransports(t, original) |
| 103 | - | daemon := newRemoteProxyDaemon() | |||
| 104 | 106 | ||||
| 105 | err := runUDPProxyRuntime(context.Background(), "remote-work", client, daemon, []string{"REMOTE=udp", "TOKEN=a=b=c"}, nil) | 107 | err := runUDPProxyRuntime(context.Background(), "remote-work", client, daemon, []string{"REMOTE=udp", "TOKEN=a=b=c"}, nil) | ||
| 106 | require.NoError(t, err) | 108 | require.NoError(t, err) | ||
| 107 | 109 | ||||
| 108 | - | got := daemon.firstHello(t) | 110 | + | got := firstHello(t, sent) |
| 109 | original.Name = "remote-work" | 111 | original.Name = "remote-work" | ||
| 110 | original.Env = []string{"REMOTE=udp", "TOKEN=a=b=c"} | 112 | original.Env = []string{"REMOTE=udp", "TOKEN=a=b=c"} | ||
| 111 | require.Equal(t, original, got) | 113 | require.Equal(t, original, got) | ||
| @@ -126,62 +128,35 @@ func remoteProxyHello() ports.Hello { | |||||
| 126 | } | 128 | } | ||
| 127 | } | 129 | } | ||
| 128 | 130 | ||||
| 129 | - | type remoteProxyClient struct { | 131 | + | func newRemoteProxyTransports(t *testing.T, hello ports.Hello) (*portsmocks.MockTransport, *portsmocks.MockTransport, <-chan ports.Frame) { |
| 130 | - | first ports.Frame | 132 | + | t.Helper() |
| 131 | - | done chan struct{} | 133 | + | client := portsmocks.NewMockTransport(t) |
| 132 | - | once sync.Once | 134 | + | daemon := portsmocks.NewMockTransport(t) |
| 133 | - | } | 135 | + | clientDone := make(chan struct{}) |
| 134 | - | 136 | + | daemonDone := make(chan struct{}) | |
| 135 | - | func newRemoteProxyClient(hello ports.Hello) *remoteProxyClient { | 137 | + | sent := make(chan ports.Frame, 1) |
| 136 | - | return &remoteProxyClient{ | 138 | + | |
| 137 | - | first: ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}, | 139 | + | scriptRecv(client, clientDone, ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}).Maybe() |
| 138 | - | done: make(chan struct{}), | 140 | + | client.EXPECT().Close().RunAndReturn(func() error { |
| 139 | - | } | 141 | + | close(clientDone) |
| 140 | - | } | 142 | + | return nil |
| 141 | - | 143 | + | }).Once() | |
| 142 | - | func (t *remoteProxyClient) Send(ports.Frame) error { return nil } | 144 | + | daemon.EXPECT().Send(mock.Anything).RunAndReturn(func(frame ports.Frame) error { |
| 143 | - | func (t *remoteProxyClient) Recv() (ports.Frame, error) { | 145 | + | sent <- frame |
| 144 | - | if t.first.Type != 0 { | 146 | + | close(daemonDone) |
| 145 | - | frame := t.first | 147 | + | return nil |
| 146 | - | t.first = ports.Frame{} | 148 | + | }).Once() |
| 147 | - | return frame, nil | 149 | + | scriptRecv(daemon, daemonDone).Once() |
| 148 | - | } | 150 | + | daemon.EXPECT().Close().Return(nil).Once() |
| 149 | - | <-t.done | 151 | + | |
| 150 | - | return ports.Frame{}, io.EOF | 152 | + | return client, daemon, sent |
| 151 | - | } | 153 | + | } |
| 152 | - | func (t *remoteProxyClient) Close() error { | 154 | + | |
| 153 | - | t.once.Do(func() { close(t.done) }) | 155 | + | func firstHello(t *testing.T, sent <-chan ports.Frame) ports.Hello { |
| 154 | - | return nil | 156 | + | t.Helper() |
| 155 | - | } | 157 | + | frame := <-sent |
| 156 | - | ||||
| 157 | - | type remoteProxyDaemon struct { | |||
| 158 | - | sent chan ports.Frame | |||
| 159 | - | done chan struct{} | |||
| 160 | - | once sync.Once | |||
| 161 | - | } | |||
| 162 | - | ||||
| 163 | - | func newRemoteProxyDaemon() *remoteProxyDaemon { | |||
| 164 | - | return &remoteProxyDaemon{sent: make(chan ports.Frame, 1), done: make(chan struct{})} | |||
| 165 | - | } | |||
| 166 | - | ||||
| 167 | - | func (t *remoteProxyDaemon) Send(frame ports.Frame) error { | |||
| 168 | - | t.sent <- frame | |||
| 169 | - | t.once.Do(func() { close(t.done) }) | |||
| 170 | - | return nil | |||
| 171 | - | } | |||
| 172 | - | func (t *remoteProxyDaemon) Recv() (ports.Frame, error) { | |||
| 173 | - | <-t.done | |||
| 174 | - | return ports.Frame{}, io.EOF | |||
| 175 | - | } | |||
| 176 | - | func (t *remoteProxyDaemon) Close() error { | |||
| 177 | - | t.once.Do(func() { close(t.done) }) | |||
| 178 | - | return nil | |||
| 179 | - | } | |||
| 180 | - | func (t *remoteProxyDaemon) firstHello(testingT *testing.T) ports.Hello { | |||
| 181 | - | testingT.Helper() | |||
| 182 | - | frame := <-t.sent | |||
| 183 | hello, err := ports.UnmarshalHello(frame.Payload) | 158 | hello, err := ports.UnmarshalHello(frame.Payload) | ||
| 184 | - | require.NoError(testingT, err) | 159 | + | require.NoError(t, err) |
| 185 | return hello | 160 | return hello | ||
| 186 | } | 161 | } | ||
| 187 | 162 | ||||