[Reproduction] Support instructions in update_invocation

Bug:b/331316158
Change-Id: I631a12baef31e8a26597a187a4b821bf6e11986b
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/5541430
Reviewed-by: Matthew Warton <mwarton@google.com>
Reviewed-by: Patrick Meiring <meiring@google.com>
Commit-Queue: Tuan Nguyen <nqmtuan@google.com>
diff --git a/README.recipes.md b/README.recipes.md
index 8abe824..1da747a 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -3559,7 +3559,7 @@
 
 &mdash; **def [assert\_enabled](/recipe_modules/resultdb/api.py#50)(self):**
 
-&mdash; **def [config\_test\_presentation](/recipe_modules/resultdb/api.py#762)(self, column_keys=(), grouping_keys=('status',)):**
+&mdash; **def [config\_test\_presentation](/recipe_modules/resultdb/api.py#770)(self, column_keys=(), grouping_keys=('status',)):**
 
 Specifies how the test results should be rendered.
 
@@ -3774,7 +3774,7 @@
 This updates the inclusions of the current invocation specified in the
 LUCI_CONTEXT.
 
-&mdash; **def [update\_invocation](/recipe_modules/resultdb/api.py#514)(self, parent_inv='', step_name=None, source_spec=None, baseline_id=None):**
+&mdash; **def [update\_invocation](/recipe_modules/resultdb/api.py#514)(self, parent_inv='', step_name=None, source_spec=None, baseline_id=None, instructions=None):**
 
 Makes a call to the UpdateInvocation API to update the invocation
 
@@ -3786,6 +3786,10 @@
   baseline_id (str): Baseline identifier for this invocation, usually of
     the format {buildbucket bucket}:{buildbucket builder name}. For example,
     'try:linux-rel'. Baselines are used to detect new tests in invocations.
+  instructions (luci.resultdb.v1.Instructions): The reproduction
+    instructions for this invocation. It may contain step instructions and
+    test result instructions. The test instructions may contain instructions
+    for test results in this invocation and in included invocations.
 
 &mdash; **def [upload\_invocation\_artifacts](/recipe_modules/resultdb/api.py#297)(self, artifacts, parent_inv=None, step_name=None):**
 
@@ -3807,7 +3811,7 @@
   A BatchCreateArtifactsResponse proto message listing the artifacts that
   were created.
 
-&mdash; **def [wrap](/recipe_modules/resultdb/api.py#613)(self, cmd, test_id_prefix='', base_variant=None, test_location_base='', base_tags=None, coerce_negative_duration=False, include=False, realm='', location_tags_file='', require_build_inv=True, exonerate_unexpected_pass=False, inv_properties='', inv_properties_file='', inherit_sources=False, sources='', sources_file='', baseline_id=''):**
+&mdash; **def [wrap](/recipe_modules/resultdb/api.py#621)(self, cmd, test_id_prefix='', base_variant=None, test_location_base='', base_tags=None, coerce_negative_duration=False, include=False, realm='', location_tags_file='', require_build_inv=True, exonerate_unexpected_pass=False, inv_properties='', inv_properties_file='', inherit_sources=False, sources='', sources_file='', baseline_id=''):**
 
 Wraps the command with ResultSink.
 
@@ -5896,10 +5900,10 @@
 &mdash; **def [RunSteps](/recipe_modules/resultdb/examples/test_presentation_default.py#9)(api):**
 ### *recipes* / [resultdb:examples/update\_invocation](/recipe_modules/resultdb/examples/update_invocation.py)
 
-[DEPS](/recipe_modules/resultdb/examples/update_invocation.py#12): [properties](#recipe_modules-properties), [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/update_invocation.py#13): [properties](#recipe_modules-properties), [resultdb](#recipe_modules-resultdb)
 
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/update_invocation.py#24)(api, invocation, gitiles_commit, gerrit_changes):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/update_invocation.py#25)(api, invocation, gitiles_commit, gerrit_changes):**
 ### *recipes* / [resultdb:examples/upload\_invocation\_artifacts](/recipe_modules/resultdb/examples/upload_invocation_artifacts.py)
 
 [DEPS](/recipe_modules/resultdb/examples/upload_invocation_artifacts.py#10): [resultdb](#recipe_modules-resultdb)
diff --git a/recipe_modules/resultdb/api.py b/recipe_modules/resultdb/api.py
index f9c029a..30c3184 100644
--- a/recipe_modules/resultdb/api.py
+++ b/recipe_modules/resultdb/api.py
@@ -515,7 +515,8 @@
                         parent_inv='',
                         step_name=None,
                         source_spec=None,
-                        baseline_id=None):
+                        baseline_id=None,
+                        instructions=None):
     """Makes a call to the UpdateInvocation API to update the invocation
 
     Args:
@@ -526,18 +527,25 @@
       baseline_id (str): Baseline identifier for this invocation, usually of
         the format {buildbucket bucket}:{buildbucket builder name}. For example,
         'try:linux-rel'. Baselines are used to detect new tests in invocations.
+      instructions (luci.resultdb.v1.Instructions): The reproduction
+        instructions for this invocation. It may contain step instructions and
+        test result instructions. The test instructions may contain instructions
+        for test results in this invocation and in included invocations.
     """
     field_mask_paths = []
     if source_spec:
       field_mask_paths.append('source_spec')
     if baseline_id:
       field_mask_paths.append('baseline_id')
+    if instructions:
+      field_mask_paths.append('instructions')
 
     req = recorder.UpdateInvocationRequest(
         invocation=invocation_pb2.Invocation(
             name=parent_inv or self.current_invocation,
             source_spec=source_spec,
-            baseline_id=baseline_id),
+            baseline_id=baseline_id,
+            instructions=instructions),
         update_mask=field_mask_pb2.FieldMask(paths=field_mask_paths),
     )
     self._rpc(
diff --git a/recipe_modules/resultdb/examples/update_invocation.py b/recipe_modules/resultdb/examples/update_invocation.py
index a8c9dab..ec5ea98 100644
--- a/recipe_modules/resultdb/examples/update_invocation.py
+++ b/recipe_modules/resultdb/examples/update_invocation.py
@@ -8,6 +8,7 @@
 
 from PB.go.chromium.org.luci.resultdb.proto.v1 import invocation as invocation_pb
 from PB.go.chromium.org.luci.resultdb.proto.v1 import common as common_pb
+from PB.go.chromium.org.luci.resultdb.proto.v1 import instruction as instruction_pb
 
 DEPS = [
     'resultdb',
@@ -33,7 +34,52 @@
               gitiles_commit=gitiles_commit,
               changelists=gerrit_changes,
           )),
-      baseline_id='try:linux-rel')
+      baseline_id='try:linux-rel',
+      instructions=instruction_pb.Instructions(
+          instructions=[
+              instruction_pb.Instruction(
+                  id="step_instruction",
+                  type=instruction_pb.InstructionType.STEP_INSTRUCTION,
+                  targeted_instructions=[
+                      instruction_pb.TargetedInstruction(
+                          targets=[
+                              instruction_pb.InstructionTarget.LOCAL,
+                          ],
+                          content="this is step content",
+                          dependencies=[
+                              instruction_pb.InstructionDependency(
+                                  invocation_id="another_inv_id",
+                                  instruction_id="another_instruction",
+                              )
+                          ],
+                      ),
+                  ],
+              ),
+              instruction_pb.Instruction(
+                  id="test_instruction",
+                  type=instruction_pb.InstructionType.TEST_RESULT_INSTRUCTION,
+                  targeted_instructions=[
+                      instruction_pb.TargetedInstruction(
+                          targets=[
+                              instruction_pb.InstructionTarget.LOCAL,
+                          ],
+                          content="this is test content",
+                          dependencies=[
+                              instruction_pb.InstructionDependency(
+                                  invocation_id="another_inv_id",
+                                  instruction_id="another_instruction",
+                              )
+                          ],
+                      ),
+                  ],
+                  instruction_filter=instruction_pb.InstructionFilter(
+                      invocation_ids=instruction_pb
+                      .InstructionFilterByInvocationID(
+                          invocation_ids=["swarming-task-1"],
+                          recursive=False,
+                      ),),
+              ),
+          ],))
 
 
 def GenTests(api):