@@ -45,7 +45,9 @@ func TestClientGatewayIntegration(t *testing.T) {
4545 testClientGatewayContainerPID1Exit ,
4646 testClientGatewayContainerMounts ,
4747 testClientGatewayContainerPID1Tty ,
48+ testClientGatewayContainerCancelPID1Tty ,
4849 testClientGatewayContainerExecTty ,
50+ testClientGatewayContainerCancelExecTty ,
4951 testClientSlowCacheRootfsRef ,
5052 testClientGatewayContainerPlatformPATH ,
5153 testClientGatewayExecError ,
@@ -923,6 +925,77 @@ func testClientGatewayContainerPID1Tty(t *testing.T, sb integration.Sandbox) {
923925 checkAllReleasable (t , c , sb , true )
924926}
925927
928+ // testClientGatewayContainerCancelPID1Tty is testing that the tty will cleanly
929+ // shutdown on context cancel
930+ func testClientGatewayContainerCancelPID1Tty (t * testing.T , sb integration.Sandbox ) {
931+ requiresLinux (t )
932+ ctx := sb .Context ()
933+
934+ c , err := New (ctx , sb .Address ())
935+ require .NoError (t , err )
936+ defer c .Close ()
937+
938+ product := "buildkit_test"
939+
940+ inputR , inputW := io .Pipe ()
941+ output := bytes .NewBuffer (nil )
942+
943+ b := func (ctx context.Context , c client.Client ) (* client.Result , error ) {
944+ ctx , cancel := context .WithTimeout (ctx , 10 * time .Second )
945+ defer cancel ()
946+
947+ st := llb .Image ("busybox:latest" )
948+
949+ def , err := st .Marshal (ctx )
950+ if err != nil {
951+ return nil , errors .Wrap (err , "failed to marshal state" )
952+ }
953+
954+ r , err := c .Solve (ctx , client.SolveRequest {
955+ Definition : def .ToPB (),
956+ })
957+ if err != nil {
958+ return nil , errors .Wrap (err , "failed to solve" )
959+ }
960+
961+ ctr , err := c .NewContainer (ctx , client.NewContainerRequest {
962+ Mounts : []client.Mount {{
963+ Dest : "/" ,
964+ MountType : pb .MountType_BIND ,
965+ Ref : r .Ref ,
966+ }},
967+ })
968+ require .NoError (t , err )
969+ defer ctr .Release (ctx )
970+
971+ prompt := newTestPrompt (ctx , t , inputW , output )
972+ pid1 , err := ctr .Start (ctx , client.StartRequest {
973+ Args : []string {"sh" },
974+ Tty : true ,
975+ Stdin : inputR ,
976+ Stdout : & nopCloser {output },
977+ Stderr : & nopCloser {output },
978+ Env : []string {fmt .Sprintf ("PS1=%s" , prompt .String ())},
979+ })
980+ require .NoError (t , err )
981+ prompt .SendExpect ("echo hi" , "hi" )
982+ cancel ()
983+
984+ err = pid1 .Wait ()
985+ require .ErrorIs (t , err , context .Canceled )
986+
987+ return & client.Result {}, err
988+ }
989+
990+ _ , err = c .Build (ctx , SolveOpt {}, product , b , nil )
991+ require .Error (t , err )
992+
993+ inputW .Close ()
994+ inputR .Close ()
995+
996+ checkAllReleasable (t , c , sb , true )
997+ }
998+
926999type testPrompt struct {
9271000 ctx context.Context
9281001 t * testing.T
@@ -1071,6 +1144,87 @@ func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) {
10711144 checkAllReleasable (t , c , sb , true )
10721145}
10731146
1147+ // testClientGatewayContainerExecTty is testing the tty shuts down cleanly
1148+ // on context.Cancel
1149+ func testClientGatewayContainerCancelExecTty (t * testing.T , sb integration.Sandbox ) {
1150+ requiresLinux (t )
1151+ ctx := sb .Context ()
1152+
1153+ c , err := New (ctx , sb .Address ())
1154+ require .NoError (t , err )
1155+ defer c .Close ()
1156+
1157+ product := "buildkit_test"
1158+
1159+ inputR , inputW := io .Pipe ()
1160+ output := bytes .NewBuffer (nil )
1161+ b := func (ctx context.Context , c client.Client ) (* client.Result , error ) {
1162+ ctx , timeout := context .WithTimeout (ctx , 10 * time .Second )
1163+ defer timeout ()
1164+ st := llb .Image ("busybox:latest" )
1165+
1166+ def , err := st .Marshal (ctx )
1167+ if err != nil {
1168+ return nil , errors .Wrap (err , "failed to marshal state" )
1169+ }
1170+
1171+ r , err := c .Solve (ctx , client.SolveRequest {
1172+ Definition : def .ToPB (),
1173+ })
1174+ if err != nil {
1175+ return nil , errors .Wrap (err , "failed to solve" )
1176+ }
1177+
1178+ ctr , err := c .NewContainer (ctx , client.NewContainerRequest {
1179+ Mounts : []client.Mount {{
1180+ Dest : "/" ,
1181+ MountType : pb .MountType_BIND ,
1182+ Ref : r .Ref ,
1183+ }},
1184+ })
1185+ require .NoError (t , err )
1186+
1187+ pid1 , err := ctr .Start (ctx , client.StartRequest {
1188+ Args : []string {"sleep" , "10" },
1189+ })
1190+ require .NoError (t , err )
1191+
1192+ defer pid1 .Wait ()
1193+ defer ctr .Release (ctx )
1194+
1195+ execCtx , cancel := context .WithCancel (ctx )
1196+ defer cancel ()
1197+
1198+ prompt := newTestPrompt (execCtx , t , inputW , output )
1199+ pid2 , err := ctr .Start (execCtx , client.StartRequest {
1200+ Args : []string {"sh" },
1201+ Tty : true ,
1202+ Stdin : inputR ,
1203+ Stdout : & nopCloser {output },
1204+ Stderr : & nopCloser {output },
1205+ Env : []string {fmt .Sprintf ("PS1=%s" , prompt .String ())},
1206+ })
1207+ require .NoError (t , err )
1208+
1209+ prompt .SendExpect ("echo hi" , "hi" )
1210+ cancel ()
1211+
1212+ err = pid2 .Wait ()
1213+ require .ErrorIs (t , err , context .Canceled )
1214+
1215+ return & client.Result {}, err
1216+ }
1217+
1218+ _ , err = c .Build (ctx , SolveOpt {}, product , b , nil )
1219+ require .Error (t , err )
1220+ require .Contains (t , err .Error (), context .Canceled .Error ())
1221+
1222+ inputW .Close ()
1223+ inputR .Close ()
1224+
1225+ checkAllReleasable (t , c , sb , true )
1226+ }
1227+
10741228func testClientSlowCacheRootfsRef (t * testing.T , sb integration.Sandbox ) {
10751229 requiresLinux (t )
10761230
0 commit comments