15
15
16
16
import static com .google .common .truth .Truth .assertThat ;
17
17
import static com .google .devtools .build .lib .testutil .MoreAsserts .assertThrows ;
18
+ import static org .junit .Assert .fail ;
18
19
20
+ import com .google .common .base .Throwables ;
19
21
import com .google .common .collect .ImmutableList ;
22
+ import com .google .common .util .concurrent .FutureCallback ;
23
+ import com .google .common .util .concurrent .Futures ;
24
+ import com .google .common .util .concurrent .ListenableFuture ;
25
+ import com .google .common .util .concurrent .ListeningScheduledExecutorService ;
26
+ import com .google .common .util .concurrent .MoreExecutors ;
27
+ import com .google .common .util .concurrent .SettableFuture ;
20
28
import com .google .devtools .build .lib .actions .ExecException ;
21
29
import com .google .devtools .build .lib .clock .JavaClock ;
22
30
import com .google .devtools .build .lib .remote .AbstractRemoteActionCache .UploadManifest ;
31
+ import com .google .devtools .build .lib .remote .TreeNodeRepository .TreeNode ;
23
32
import com .google .devtools .build .lib .remote .util .DigestUtil ;
33
+ import com .google .devtools .build .lib .remote .util .DigestUtil .ActionKey ;
34
+ import com .google .devtools .build .lib .remote .util .Utils ;
35
+ import com .google .devtools .build .lib .util .io .FileOutErr ;
24
36
import com .google .devtools .build .lib .vfs .DigestHashFunction ;
25
37
import com .google .devtools .build .lib .vfs .FileSystem ;
26
38
import com .google .devtools .build .lib .vfs .FileSystemUtils ;
27
39
import com .google .devtools .build .lib .vfs .Path ;
28
40
import com .google .devtools .build .lib .vfs .inmemoryfs .InMemoryFileSystem ;
29
41
import com .google .devtools .remoteexecution .v1test .ActionResult ;
42
+ import com .google .devtools .remoteexecution .v1test .Command ;
43
+ import com .google .devtools .remoteexecution .v1test .Digest ;
44
+ import com .google .devtools .remoteexecution .v1test .OutputFile ;
45
+ import java .io .IOException ;
46
+ import java .io .OutputStream ;
47
+ import java .util .ArrayList ;
48
+ import java .util .Collection ;
49
+ import java .util .HashMap ;
50
+ import java .util .List ;
51
+ import java .util .Map ;
52
+ import java .util .concurrent .Executors ;
53
+ import java .util .concurrent .atomic .AtomicInteger ;
54
+ import javax .annotation .Nullable ;
55
+ import org .junit .AfterClass ;
30
56
import org .junit .Before ;
57
+ import org .junit .BeforeClass ;
31
58
import org .junit .Test ;
32
59
import org .junit .runner .RunWith ;
33
60
import org .junit .runners .JUnit4 ;
@@ -40,13 +67,25 @@ public class AbstractRemoteActionCacheTests {
40
67
private Path execRoot ;
41
68
private final DigestUtil digestUtil = new DigestUtil (DigestHashFunction .SHA256 );
42
69
70
+ private static ListeningScheduledExecutorService retryService ;
71
+
72
+ @ BeforeClass
73
+ public static void beforeEverything () {
74
+ retryService = MoreExecutors .listeningDecorator (Executors .newScheduledThreadPool (1 ));
75
+ }
76
+
43
77
@ Before
44
78
public void setUp () throws Exception {
45
79
fs = new InMemoryFileSystem (new JavaClock (), DigestHashFunction .SHA256 );
46
80
execRoot = fs .getPath ("/execroot" );
47
81
execRoot .createDirectory ();
48
82
}
49
83
84
+ @ AfterClass
85
+ public static void afterEverything () {
86
+ retryService .shutdownNow ();
87
+ }
88
+
50
89
@ Test
51
90
public void uploadSymlinkAsFile () throws Exception {
52
91
ActionResult .Builder result = ActionResult .newBuilder ();
@@ -92,4 +131,118 @@ public void uploadSymlinkInDirectory() throws Exception {
92
131
.hasMessageThat ()
93
132
.contains ("Only regular files and directories may be uploaded to a remote cache." );
94
133
}
134
+
135
+ @ Test
136
+ public void onErrorWaitForRemainingDownloadsToComplete () throws Exception {
137
+ // If one or more downloads of output files / directories fail then the code should
138
+ // wait for all downloads to have been completed before it tries to clean up partially
139
+ // downloaded files.
140
+
141
+ Path stdout = fs .getPath ("/execroot/stdout" );
142
+ Path stderr = fs .getPath ("/execroot/stderr" );
143
+
144
+ Map <Digest , ListenableFuture <byte []>> downloadResults = new HashMap <>();
145
+ Path file1 = fs .getPath ("/execroot/file1" );
146
+ Digest digest1 = digestUtil .compute ("file1" .getBytes ("UTF-8" ));
147
+ downloadResults .put (digest1 , Futures .immediateFuture ("file1" .getBytes ("UTF-8" )));
148
+ Path file2 = fs .getPath ("/execroot/file2" );
149
+ Digest digest2 = digestUtil .compute ("file2" .getBytes ("UTF-8" ));
150
+ downloadResults .put (digest2 , Futures .immediateFailedFuture (new IOException ("download failed" )));
151
+ Path file3 = fs .getPath ("/execroot/file3" );
152
+ Digest digest3 = digestUtil .compute ("file3" .getBytes ("UTF-8" ));
153
+ downloadResults .put (digest3 , Futures .immediateFuture ("file3" .getBytes ("UTF-8" )));
154
+
155
+ RemoteOptions options = new RemoteOptions ();
156
+ RemoteRetrier retrier = new RemoteRetrier (options , (e ) -> false , retryService ,
157
+ Retrier .ALLOW_ALL_CALLS );
158
+ List <ListenableFuture <?>> blockingDownloads = new ArrayList <>();
159
+ AtomicInteger numSuccess = new AtomicInteger ();
160
+ AtomicInteger numFailures = new AtomicInteger ();
161
+ AbstractRemoteActionCache cache = new DefaultRemoteActionCache (options , digestUtil , retrier ) {
162
+ @ Override
163
+ public ListenableFuture <Void > downloadBlob (Digest digest , OutputStream out ) {
164
+ SettableFuture <Void > result = SettableFuture .create ();
165
+ Futures .addCallback (downloadResults .get (digest ), new FutureCallback <byte []>() {
166
+ @ Override
167
+ public void onSuccess (byte [] bytes ) {
168
+ numSuccess .incrementAndGet ();
169
+ try {
170
+ out .write (bytes );
171
+ out .close ();
172
+ result .set (null );
173
+ } catch (IOException e ) {
174
+ result .setException (e );
175
+ }
176
+ }
177
+
178
+ @ Override
179
+ public void onFailure (Throwable throwable ) {
180
+ numFailures .incrementAndGet ();
181
+ result .setException (throwable );
182
+ }
183
+ }, MoreExecutors .directExecutor ());
184
+ return result ;
185
+ }
186
+
187
+ @ Override
188
+ protected <T > T getFromFuture (ListenableFuture <T > f )
189
+ throws IOException , InterruptedException {
190
+ blockingDownloads .add (f );
191
+ return Utils .getFromFuture (f );
192
+ }
193
+ };
194
+
195
+ ActionResult result = ActionResult .newBuilder ()
196
+ .setExitCode (0 )
197
+ .addOutputFiles (OutputFile .newBuilder ().setPath (file1 .getPathString ()).setDigest (digest1 ))
198
+ .addOutputFiles (OutputFile .newBuilder ().setPath (file2 .getPathString ()).setDigest (digest2 ))
199
+ .addOutputFiles (OutputFile .newBuilder ().setPath (file3 .getPathString ()).setDigest (digest3 ))
200
+ .build ();
201
+ try {
202
+ cache .download (result , execRoot , new FileOutErr (stdout , stderr ));
203
+ fail ("Expected IOException" );
204
+ } catch (IOException e ) {
205
+ assertThat (numSuccess .get ()).isEqualTo (2 );
206
+ assertThat (numFailures .get ()).isEqualTo (1 );
207
+ assertThat (blockingDownloads ).hasSize (3 );
208
+ assertThat (Throwables .getRootCause (e )).hasMessageThat ().isEqualTo ("download failed" );
209
+ }
210
+ }
211
+
212
+ private static class DefaultRemoteActionCache extends AbstractRemoteActionCache {
213
+
214
+ public DefaultRemoteActionCache (RemoteOptions options ,
215
+ DigestUtil digestUtil , Retrier retrier ) {
216
+ super (options , digestUtil , retrier );
217
+ }
218
+
219
+ @ Override
220
+ public void ensureInputsPresent (TreeNodeRepository repository , Path execRoot , TreeNode root ,
221
+ Command command ) throws IOException , InterruptedException {
222
+ throw new UnsupportedOperationException ();
223
+ }
224
+
225
+ @ Nullable
226
+ @ Override
227
+ ActionResult getCachedActionResult (ActionKey actionKey )
228
+ throws IOException , InterruptedException {
229
+ throw new UnsupportedOperationException ();
230
+ }
231
+
232
+ @ Override
233
+ void upload (ActionKey actionKey , Path execRoot , Collection <Path > files , FileOutErr outErr ,
234
+ boolean uploadAction ) throws ExecException , IOException , InterruptedException {
235
+ throw new UnsupportedOperationException ();
236
+ }
237
+
238
+ @ Override
239
+ protected ListenableFuture <Void > downloadBlob (Digest digest , OutputStream out ) {
240
+ throw new UnsupportedOperationException ();
241
+ }
242
+
243
+ @ Override
244
+ public void close () {
245
+ throw new UnsupportedOperationException ();
246
+ }
247
+ }
95
248
}
0 commit comments