25
25
import com .google .devtools .build .lib .actions .ActionExecutionContext ;
26
26
import com .google .devtools .build .lib .actions .ActionInput ;
27
27
import com .google .devtools .build .lib .actions .ActionInputHelper ;
28
+ import com .google .devtools .build .lib .actions .Artifact ;
29
+ import com .google .devtools .build .lib .actions .Artifact .DerivedArtifact ;
28
30
import com .google .devtools .build .lib .actions .Artifact .SpecialArtifact ;
31
+ import com .google .devtools .build .lib .actions .Artifact .TreeFileArtifact ;
29
32
import com .google .devtools .build .lib .actions .ArtifactPathResolver ;
30
33
import com .google .devtools .build .lib .actions .EnvironmentalExecException ;
31
34
import com .google .devtools .build .lib .actions .ExecException ;
32
35
import com .google .devtools .build .lib .actions .ExecutionRequirements ;
36
+ import com .google .devtools .build .lib .actions .FileArtifactValue ;
33
37
import com .google .devtools .build .lib .actions .ResourceSet ;
34
38
import com .google .devtools .build .lib .actions .SimpleSpawn ;
35
39
import com .google .devtools .build .lib .actions .Spawn ;
36
40
import com .google .devtools .build .lib .actions .SpawnContinuation ;
37
41
import com .google .devtools .build .lib .actions .SpawnResult ;
38
42
import com .google .devtools .build .lib .actions .TestExecException ;
43
+ import com .google .devtools .build .lib .actions .cache .MetadataHandler ;
39
44
import com .google .devtools .build .lib .analysis .actions .SpawnAction ;
40
45
import com .google .devtools .build .lib .analysis .test .TestAttempt ;
41
46
import com .google .devtools .build .lib .analysis .test .TestResult ;
50
55
import com .google .devtools .build .lib .events .Reporter ;
51
56
import com .google .devtools .build .lib .server .FailureDetails .Execution .Code ;
52
57
import com .google .devtools .build .lib .server .FailureDetails .TestAction ;
58
+ import com .google .devtools .build .lib .skyframe .TreeArtifactValue ;
53
59
import com .google .devtools .build .lib .util .Pair ;
54
60
import com .google .devtools .build .lib .util .io .FileOutErr ;
55
61
import com .google .devtools .build .lib .vfs .FileStatus ;
67
73
import java .util .List ;
68
74
import java .util .Map ;
69
75
import java .util .TreeMap ;
76
+ import java .util .concurrent .ConcurrentHashMap ;
77
+ import java .util .concurrent .ConcurrentMap ;
70
78
import javax .annotation .Nullable ;
71
79
72
80
/** Runs TestRunnerAction actions. */
@@ -135,7 +143,7 @@ public TestRunnerSpawn createTestRunnerSpawn(
135
143
action .getTestProperties ().isPersistentTestRunner ()
136
144
? action .getTools ()
137
145
: NestedSetBuilder .emptySet (Order .STABLE_ORDER ),
138
- ImmutableSet . copyOf (action . getSpawnOutputs () ),
146
+ createSpawnOutputs (action ),
139
147
localResourceUsage );
140
148
Path execRoot = actionExecutionContext .getExecRoot ();
141
149
ArtifactPathResolver pathResolver = actionExecutionContext .getPathResolver ();
@@ -146,6 +154,21 @@ public TestRunnerSpawn createTestRunnerSpawn(
146
154
action , actionExecutionContext , spawn , tmpDir , workingDirectory , execRoot );
147
155
}
148
156
157
+ private ImmutableSet <ActionInput > createSpawnOutputs (TestRunnerAction action ) {
158
+ ImmutableSet .Builder <ActionInput > builder = ImmutableSet .builder ();
159
+ for (ActionInput output : action .getSpawnOutputs ()) {
160
+ if (output .getExecPath ().equals (action .getXmlOutputPath ())) {
161
+ // HACK: Convert type of test.xml from BasicActionInput to DerivedArtifact. We want to
162
+ // inject metadata of test.xml if it is generated remotely and it's currently only possible
163
+ // to inject Artifact.
164
+ builder .add (createArtifactOutput (action , output .getExecPath ()));
165
+ } else {
166
+ builder .add (output );
167
+ }
168
+ }
169
+ return builder .build ();
170
+ }
171
+
149
172
private static ImmutableList <Pair <String , Path >> renameOutputs (
150
173
ActionExecutionContext actionExecutionContext ,
151
174
TestRunnerAction action ,
@@ -294,11 +317,84 @@ private static Map<String, String> setupEnvironment(
294
317
relativeTmpDir = tmpDir .asFragment ();
295
318
}
296
319
return DEFAULT_LOCAL_POLICY .computeTestEnvironment (
297
- action ,
298
- clientEnv ,
299
- getTimeout (action ),
300
- runfilesDir .relativeTo (execRoot ),
301
- relativeTmpDir );
320
+ action , clientEnv , getTimeout (action ), runfilesDir .relativeTo (execRoot ), relativeTmpDir );
321
+ }
322
+
323
+ static class TestMetadataHandler implements MetadataHandler {
324
+ private final MetadataHandler metadataHandler ;
325
+ private final ImmutableSet <Artifact > outputs ;
326
+ private final ConcurrentMap <Artifact , FileArtifactValue > fileMetadataMap =
327
+ new ConcurrentHashMap <>();
328
+
329
+ TestMetadataHandler (MetadataHandler metadataHandler , ImmutableSet <Artifact > outputs ) {
330
+ this .metadataHandler = metadataHandler ;
331
+ this .outputs = outputs ;
332
+ }
333
+
334
+ @ Nullable
335
+ @ Override
336
+ public ActionInput getInput (String execPath ) {
337
+ return metadataHandler .getInput (execPath );
338
+ }
339
+
340
+ @ Nullable
341
+ @ Override
342
+ public FileArtifactValue getMetadata (ActionInput input ) throws IOException {
343
+ return metadataHandler .getMetadata (input );
344
+ }
345
+
346
+ @ Override
347
+ public void setDigestForVirtualArtifact (Artifact artifact , byte [] digest ) {
348
+ metadataHandler .setDigestForVirtualArtifact (artifact , digest );
349
+ }
350
+
351
+ @ Override
352
+ public FileArtifactValue constructMetadataForDigest (
353
+ Artifact output , FileStatus statNoFollow , byte [] injectedDigest ) throws IOException {
354
+ return metadataHandler .constructMetadataForDigest (output , statNoFollow , injectedDigest );
355
+ }
356
+
357
+ @ Override
358
+ public ImmutableSet <TreeFileArtifact > getTreeArtifactChildren (SpecialArtifact treeArtifact ) {
359
+ return metadataHandler .getTreeArtifactChildren (treeArtifact );
360
+ }
361
+
362
+ @ Override
363
+ public TreeArtifactValue getTreeArtifactValue (SpecialArtifact treeArtifact ) throws IOException {
364
+ return metadataHandler .getTreeArtifactValue (treeArtifact );
365
+ }
366
+
367
+ @ Override
368
+ public void markOmitted (Artifact output ) {
369
+ metadataHandler .markOmitted (output );
370
+ }
371
+
372
+ @ Override
373
+ public boolean artifactOmitted (Artifact artifact ) {
374
+ return metadataHandler .artifactOmitted (artifact );
375
+ }
376
+
377
+ @ Override
378
+ public void resetOutputs (Iterable <? extends Artifact > outputs ) {
379
+ metadataHandler .resetOutputs (outputs );
380
+ }
381
+
382
+ @ Override
383
+ public void injectFile (Artifact output , FileArtifactValue metadata ) {
384
+ if (outputs .contains (output )) {
385
+ metadataHandler .injectFile (output , metadata );
386
+ }
387
+ fileMetadataMap .put (output , metadata );
388
+ }
389
+
390
+ @ Override
391
+ public void injectTree (SpecialArtifact output , TreeArtifactValue tree ) {
392
+ metadataHandler .injectTree (output , tree );
393
+ }
394
+
395
+ public boolean fileInjected (Artifact output ) {
396
+ return fileMetadataMap .containsKey (output );
397
+ }
302
398
}
303
399
304
400
private TestAttemptContinuation beginTestAttempt (
@@ -317,12 +413,26 @@ private TestAttemptContinuation beginTestAttempt(
317
413
createStreamedTestOutput (
318
414
Reporter .outErrForReporter (actionExecutionContext .getEventHandler ()), out );
319
415
}
416
+
417
+ // We use TestMetadataHandler here mainly because the one provided by actionExecutionContext
418
+ // doesn't allow to inject undeclared outputs and test.xml is undeclared by the test action.
419
+ TestMetadataHandler testMetadataHandler = null ;
420
+ if (actionExecutionContext .getMetadataHandler () != null ) {
421
+ testMetadataHandler =
422
+ new TestMetadataHandler (
423
+ actionExecutionContext .getMetadataHandler (), testAction .getOutputs ());
424
+ }
425
+
320
426
long startTimeMillis = actionExecutionContext .getClock ().currentTimeMillis ();
321
427
SpawnStrategyResolver resolver = actionExecutionContext .getContext (SpawnStrategyResolver .class );
322
428
SpawnContinuation spawnContinuation ;
323
429
try {
324
430
spawnContinuation =
325
- resolver .beginExecution (spawn , actionExecutionContext .withFileOutErr (testOutErr ));
431
+ resolver .beginExecution (
432
+ spawn ,
433
+ actionExecutionContext
434
+ .withFileOutErr (testOutErr )
435
+ .withMetadataHandler (testMetadataHandler ));
326
436
} catch (InterruptedException e ) {
327
437
if (streamed != null ) {
328
438
streamed .close ();
@@ -332,6 +442,7 @@ private TestAttemptContinuation beginTestAttempt(
332
442
}
333
443
return new BazelTestAttemptContinuation (
334
444
testAction ,
445
+ testMetadataHandler ,
335
446
actionExecutionContext ,
336
447
spawn ,
337
448
resolvedPaths ,
@@ -387,6 +498,12 @@ private static BuildEventStreamProtos.TestResult.ExecutionInfo extractExecutionI
387
498
return executionInfo .build ();
388
499
}
389
500
501
+ private static Artifact .DerivedArtifact createArtifactOutput (
502
+ TestRunnerAction action , PathFragment outputPath ) {
503
+ Artifact .DerivedArtifact testLog = (Artifact .DerivedArtifact ) action .getTestLog ();
504
+ return new DerivedArtifact (testLog .getRoot (), outputPath , testLog .getArtifactOwner ());
505
+ }
506
+
390
507
/**
391
508
* A spawn to generate a test.xml file from the test log. This is only used if the test does not
392
509
* generate a test.xml file itself.
@@ -423,7 +540,7 @@ private static Spawn createXmlGeneratingSpawn(
423
540
/*inputs=*/ NestedSetBuilder .create (
424
541
Order .STABLE_ORDER , action .getTestXmlGeneratorScript (), action .getTestLog ()),
425
542
/*tools=*/ NestedSetBuilder .emptySet (Order .STABLE_ORDER ),
426
- /*outputs=*/ ImmutableSet .of (ActionInputHelper . fromPath ( action .getXmlOutputPath ())),
543
+ /*outputs=*/ ImmutableSet .of (createArtifactOutput ( action , action .getXmlOutputPath ())),
427
544
SpawnAction .DEFAULT_RESOURCE_SET );
428
545
}
429
546
@@ -576,6 +693,7 @@ public void finalizeCancelledTest(List<FailedAttemptResult> failedAttempts) thro
576
693
577
694
private final class BazelTestAttemptContinuation extends TestAttemptContinuation {
578
695
private final TestRunnerAction testAction ;
696
+ @ Nullable private final TestMetadataHandler testMetadataHandler ;
579
697
private final ActionExecutionContext actionExecutionContext ;
580
698
private final Spawn spawn ;
581
699
private final ResolvedPaths resolvedPaths ;
@@ -588,6 +706,7 @@ private final class BazelTestAttemptContinuation extends TestAttemptContinuation
588
706
589
707
BazelTestAttemptContinuation (
590
708
TestRunnerAction testAction ,
709
+ @ Nullable TestMetadataHandler testMetadataHandler ,
591
710
ActionExecutionContext actionExecutionContext ,
592
711
Spawn spawn ,
593
712
ResolvedPaths resolvedPaths ,
@@ -598,6 +717,7 @@ private final class BazelTestAttemptContinuation extends TestAttemptContinuation
598
717
TestResultData .Builder testResultDataBuilder ,
599
718
ImmutableList <SpawnResult > spawnResults ) {
600
719
this .testAction = testAction ;
720
+ this .testMetadataHandler = testMetadataHandler ;
601
721
this .actionExecutionContext = actionExecutionContext ;
602
722
this .spawn = spawn ;
603
723
this .resolvedPaths = resolvedPaths ;
@@ -638,6 +758,7 @@ public TestAttemptContinuation execute()
638
758
if (!nextContinuation .isDone ()) {
639
759
return new BazelTestAttemptContinuation (
640
760
testAction ,
761
+ testMetadataHandler ,
641
762
actionExecutionContext ,
642
763
spawn ,
643
764
resolvedPaths ,
@@ -727,6 +848,7 @@ public TestAttemptContinuation execute()
727
848
appendCoverageLog (coverageOutErr , fileOutErr );
728
849
return new BazelCoveragePostProcessingContinuation (
729
850
testAction ,
851
+ testMetadataHandler ,
730
852
actionExecutionContext ,
731
853
spawn ,
732
854
resolvedPaths ,
@@ -762,15 +884,21 @@ public TestAttemptContinuation execute()
762
884
throw new EnvironmentalExecException (e , Code .TEST_OUT_ERR_IO_EXCEPTION );
763
885
}
764
886
887
+ Path xmlOutputPath = resolvedPaths .getXmlOutputPath ();
888
+ boolean testXmlGenerated = xmlOutputPath .exists ();
889
+ if (!testXmlGenerated && testMetadataHandler != null ) {
890
+ testXmlGenerated =
891
+ testMetadataHandler .fileInjected (
892
+ createArtifactOutput (testAction , testAction .getXmlOutputPath ()));
893
+ }
765
894
766
895
// If the test did not create a test.xml, and --experimental_split_xml_generation is enabled,
767
896
// then we run a separate action to create a test.xml from test.log. We do this as a spawn
768
897
// rather than doing it locally in-process, as the test.log file may only exist remotely (when
769
898
// remote execution is enabled), and we do not want to have to download it.
770
- Path xmlOutputPath = resolvedPaths .getXmlOutputPath ();
771
899
if (executionOptions .splitXmlGeneration
772
900
&& fileOutErr .getOutputPath ().exists ()
773
- && !xmlOutputPath . exists () ) {
901
+ && !testXmlGenerated ) {
774
902
Spawn xmlGeneratingSpawn =
775
903
createXmlGeneratingSpawn (testAction , spawn .getEnvironment (), spawnResults .get (0 ));
776
904
SpawnStrategyResolver spawnStrategyResolver =
@@ -781,7 +909,10 @@ public TestAttemptContinuation execute()
781
909
try {
782
910
SpawnContinuation xmlContinuation =
783
911
spawnStrategyResolver .beginExecution (
784
- xmlGeneratingSpawn , actionExecutionContext .withFileOutErr (xmlSpawnOutErr ));
912
+ xmlGeneratingSpawn ,
913
+ actionExecutionContext
914
+ .withFileOutErr (xmlSpawnOutErr )
915
+ .withMetadataHandler (testMetadataHandler ));
785
916
return new BazelXmlCreationContinuation (
786
917
resolvedPaths , xmlSpawnOutErr , testResultDataBuilder , spawnResults , xmlContinuation );
787
918
} catch (InterruptedException e ) {
@@ -879,6 +1010,7 @@ public TestAttemptContinuation execute() throws InterruptedException, ExecExcept
879
1010
880
1011
private final class BazelCoveragePostProcessingContinuation extends TestAttemptContinuation {
881
1012
private final ResolvedPaths resolvedPaths ;
1013
+ @ Nullable private final TestMetadataHandler testMetadataHandler ;
882
1014
private final FileOutErr fileOutErr ;
883
1015
private final Closeable streamed ;
884
1016
private final TestResultData .Builder testResultDataBuilder ;
@@ -890,6 +1022,7 @@ private final class BazelCoveragePostProcessingContinuation extends TestAttemptC
890
1022
891
1023
BazelCoveragePostProcessingContinuation (
892
1024
TestRunnerAction testAction ,
1025
+ @ Nullable TestMetadataHandler testMetadataHandler ,
893
1026
ActionExecutionContext actionExecutionContext ,
894
1027
Spawn spawn ,
895
1028
ResolvedPaths resolvedPaths ,
@@ -899,6 +1032,7 @@ private final class BazelCoveragePostProcessingContinuation extends TestAttemptC
899
1032
ImmutableList <SpawnResult > primarySpawnResults ,
900
1033
SpawnContinuation spawnContinuation ) {
901
1034
this .testAction = testAction ;
1035
+ this .testMetadataHandler = testMetadataHandler ;
902
1036
this .actionExecutionContext = actionExecutionContext ;
903
1037
this .spawn = spawn ;
904
1038
this .resolvedPaths = resolvedPaths ;
@@ -923,6 +1057,7 @@ public TestAttemptContinuation execute() throws InterruptedException, ExecExcept
923
1057
if (!nextContinuation .isDone ()) {
924
1058
return new BazelCoveragePostProcessingContinuation (
925
1059
testAction ,
1060
+ testMetadataHandler ,
926
1061
actionExecutionContext ,
927
1062
spawn ,
928
1063
resolvedPaths ,
@@ -958,6 +1093,7 @@ public TestAttemptContinuation execute() throws InterruptedException, ExecExcept
958
1093
959
1094
return new BazelTestAttemptContinuation (
960
1095
testAction ,
1096
+ testMetadataHandler ,
961
1097
actionExecutionContext ,
962
1098
spawn ,
963
1099
resolvedPaths ,
0 commit comments