[python3] Make resultdb module python3 compatible

Bug: 1239059
Change-Id: Ie688a7ed0c038bb44e2bb1cdec49b5d6d836681b
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/3173257
Reviewed-by: Robbie Iannucci <iannucci@chromium.org>
Commit-Queue: Yuanjun Huang <yuanjunh@google.com>
diff --git a/README.recipes.md b/README.recipes.md
index ab27951..1f58d8e 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -26,7 +26,7 @@
   * [python](#recipe_modules-python) (Python3 ✅) &mdash; Provides methods for running python scripts correctly.
   * [random](#recipe_modules-random) (Python3 ✅) &mdash; Allows randomness in recipes.
   * [raw_io](#recipe_modules-raw_io) (Python3 ✅) &mdash; Provides objects for reading and writing raw data to and from steps.
-  * [resultdb](#recipe_modules-resultdb) &mdash; API for interacting with the ResultDB service.
+  * [resultdb](#recipe_modules-resultdb) (Python3 ✅) &mdash; API for interacting with the ResultDB service.
   * [runtime](#recipe_modules-runtime) (Python3 ✅)
   * [scheduler](#recipe_modules-scheduler) (Python3 ✅) &mdash; API for interacting with the LUCI Scheduler service.
   * [service_account](#recipe_modules-service_account) (Python3 ✅) &mdash; API for getting OAuth2 access tokens for LUCI tasks or private keys.
@@ -135,15 +135,15 @@
   * [python:tests/infra_failing_step](#recipes-python_tests_infra_failing_step) (Python3 ✅) &mdash; Tests for api.
   * [random:tests/full](#recipes-random_tests_full) (Python3 ✅)
   * [raw_io:examples/full](#recipes-raw_io_examples_full) (Python3 ✅)
-  * [resultdb:examples/exonerate](#recipes-resultdb_examples_exonerate)
-  * [resultdb:examples/include](#recipes-resultdb_examples_include)
-  * [resultdb:examples/query](#recipes-resultdb_examples_query)
-  * [resultdb:examples/query_test_result_statistics](#recipes-resultdb_examples_query_test_result_statistics)
-  * [resultdb:examples/result_history](#recipes-resultdb_examples_result_history)
-  * [resultdb:examples/resultsink](#recipes-resultdb_examples_resultsink)
-  * [resultdb:examples/test_presentation](#recipes-resultdb_examples_test_presentation)
-  * [resultdb:examples/test_presentation_default](#recipes-resultdb_examples_test_presentation_default)
-  * [resultdb:examples/upload_invocation_artifacts](#recipes-resultdb_examples_upload_invocation_artifacts)
+  * [resultdb:examples/exonerate](#recipes-resultdb_examples_exonerate) (Python3 ✅)
+  * [resultdb:examples/include](#recipes-resultdb_examples_include) (Python3 ✅)
+  * [resultdb:examples/query](#recipes-resultdb_examples_query) (Python3 ✅)
+  * [resultdb:examples/query_test_result_statistics](#recipes-resultdb_examples_query_test_result_statistics) (Python3 ✅)
+  * [resultdb:examples/result_history](#recipes-resultdb_examples_result_history) (Python3 ✅)
+  * [resultdb:examples/resultsink](#recipes-resultdb_examples_resultsink) (Python3 ✅)
+  * [resultdb:examples/test_presentation](#recipes-resultdb_examples_test_presentation) (Python3 ✅)
+  * [resultdb:examples/test_presentation_default](#recipes-resultdb_examples_test_presentation_default) (Python3 ✅)
+  * [resultdb:examples/upload_invocation_artifacts](#recipes-resultdb_examples_upload_invocation_artifacts) (Python3 ✅)
   * [runtime:tests/full](#recipes-runtime_tests_full) (Python3 ✅)
   * [scheduler:examples/emit_triggers](#recipes-scheduler_examples_emit_triggers) (Python3 ✅) &mdash; This file is a recipe demonstrating emitting triggers to LUCI Scheduler.
   * [scheduler:examples/host](#recipes-scheduler_examples_host) (Python3 ✅) &mdash; This file is a recipe demonstrating reading/mocking scheduler host.
@@ -2793,9 +2793,9 @@
      log when the step has a non-SUCCESS status.
 ### *recipe_modules* / [resultdb](/recipe_modules/resultdb)
 
-[DEPS](/recipe_modules/resultdb/__init__.py#6): [context](#recipe_modules-context), [futures](#recipe_modules-futures), [json](#recipe_modules-json), [raw\_io](#recipe_modules-raw_io), [step](#recipe_modules-step), [time](#recipe_modules-time), [uuid](#recipe_modules-uuid)
+[DEPS](/recipe_modules/resultdb/__init__.py#7): [context](#recipe_modules-context), [futures](#recipe_modules-futures), [json](#recipe_modules-json), [raw\_io](#recipe_modules-raw_io), [step](#recipe_modules-step), [time](#recipe_modules-time), [uuid](#recipe_modules-uuid)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
 API for interacting with the ResultDB service.
 
@@ -4620,67 +4620,67 @@
 &mdash; **def [RunSteps](/recipe_modules/raw_io/examples/full.py#18)(api):**
 ### *recipes* / [resultdb:examples/exonerate](/recipe_modules/resultdb/examples/exonerate.py)
 
-[DEPS](/recipe_modules/resultdb/examples/exonerate.py#11): [context](#recipe_modules-context), [json](#recipe_modules-json), [properties](#recipe_modules-properties), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
+[DEPS](/recipe_modules/resultdb/examples/exonerate.py#13): [context](#recipe_modules-context), [json](#recipe_modules-json), [properties](#recipe_modules-properties), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/exonerate.py#37)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/exonerate.py#39)(api):**
 ### *recipes* / [resultdb:examples/include](/recipe_modules/resultdb/examples/include.py)
 
-[DEPS](/recipe_modules/resultdb/examples/include.py#11): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/include.py#13): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/include.py#17)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/include.py#19)(api):**
 ### *recipes* / [resultdb:examples/query](/recipe_modules/resultdb/examples/query.py)
 
-[DEPS](/recipe_modules/resultdb/examples/query.py#15): [buildbucket](#recipe_modules-buildbucket), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
+[DEPS](/recipe_modules/resultdb/examples/query.py#17): [buildbucket](#recipe_modules-buildbucket), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/query.py#22)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/query.py#24)(api):**
 ### *recipes* / [resultdb:examples/query\_test\_result\_statistics](/recipe_modules/resultdb/examples/query_test_result_statistics.py)
 
-[DEPS](/recipe_modules/resultdb/examples/query_test_result_statistics.py#10): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/query_test_result_statistics.py#12): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/query_test_result_statistics.py#16)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/query_test_result_statistics.py#18)(api):**
 ### *recipes* / [resultdb:examples/result\_history](/recipe_modules/resultdb/examples/result_history.py)
 
-[DEPS](/recipe_modules/resultdb/examples/result_history.py#11): [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/result_history.py#13): [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/result_history.py#16)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/result_history.py#18)(api):**
 ### *recipes* / [resultdb:examples/resultsink](/recipe_modules/resultdb/examples/resultsink.py)
 
-[DEPS](/recipe_modules/resultdb/examples/resultsink.py#7): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
+[DEPS](/recipe_modules/resultdb/examples/resultsink.py#9): [context](#recipe_modules-context), [resultdb](#recipe_modules-resultdb), [step](#recipe_modules-step)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/resultsink.py#14)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/resultsink.py#16)(api):**
 ### *recipes* / [resultdb:examples/test\_presentation](/recipe_modules/resultdb/examples/test_presentation.py)
 
-[DEPS](/recipe_modules/resultdb/examples/test_presentation.py#5): [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/test_presentation.py#7): [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/test_presentation.py#9)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/test_presentation.py#11)(api):**
 ### *recipes* / [resultdb:examples/test\_presentation\_default](/recipe_modules/resultdb/examples/test_presentation_default.py)
 
-[DEPS](/recipe_modules/resultdb/examples/test_presentation_default.py#5): [resultdb](#recipe_modules-resultdb)
+[DEPS](/recipe_modules/resultdb/examples/test_presentation_default.py#7): [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/test_presentation_default.py#9)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/test_presentation_default.py#11)(api):**
 ### *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)
+[DEPS](/recipe_modules/resultdb/examples/upload_invocation_artifacts.py#12): [resultdb](#recipe_modules-resultdb)
 
-PYTHON_VERSION_COMPATIBILITY: PY2
+PYTHON_VERSION_COMPATIBILITY: PY2+3
 
-&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/upload_invocation_artifacts.py#15)(api):**
+&mdash; **def [RunSteps](/recipe_modules/resultdb/examples/upload_invocation_artifacts.py#17)(api):**
 ### *recipes* / [runtime:tests/full](/recipe_modules/runtime/tests/full.py)
 
 [DEPS](/recipe_modules/runtime/tests/full.py#9): [runtime](#recipe_modules-runtime), [step](#recipe_modules-step)
diff --git a/recipe_modules/resultdb/__init__.py b/recipe_modules/resultdb/__init__.py
index b9486c4..f214ace 100644
--- a/recipe_modules/resultdb/__init__.py
+++ b/recipe_modules/resultdb/__init__.py
@@ -2,6 +2,7 @@
 # Use of this source code is governed under the Apache License, Version 2.0
 # that can be found in the LICENSE file.
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
 
 DEPS = [
     'context',
diff --git a/recipe_modules/resultdb/api.py b/recipe_modules/resultdb/api.py
index 6ca98dc..9575e54 100644
--- a/recipe_modules/resultdb/api.py
+++ b/recipe_modules/resultdb/api.py
@@ -356,7 +356,7 @@
               contents=art['contents'],
           ),
       )
-      for art_id, art in artifacts.iteritems()
+      for art_id, art in iteritems(artifacts)
     ])
 
     res = self._rpc(
diff --git a/recipe_modules/resultdb/common.py b/recipe_modules/resultdb/common.py
index 3282013..ce0ccd7 100644
--- a/recipe_modules/resultdb/common.py
+++ b/recipe_modules/resultdb/common.py
@@ -58,7 +58,7 @@
         json.dumps(jsonish, sort_keys=True, indent=2 if pretty else None)
     )
 
-  for inv_id, inv in iteritems(inv_bundle):
+  for inv_id, inv in sorted(iteritems(inv_bundle)):
     assert isinstance(inv, Invocation), inv
     if inv.proto.ListFields():  # if something is set
       add_line(inv_id, 'invocation', inv.proto)
diff --git a/recipe_modules/resultdb/examples/exonerate.py b/recipe_modules/resultdb/examples/exonerate.py
index 025a731..769b3d3 100644
--- a/recipe_modules/resultdb/examples/exonerate.py
+++ b/recipe_modules/resultdb/examples/exonerate.py
@@ -8,6 +8,8 @@
 from PB.go.chromium.org.luci.lucictx import sections as sections_pb2
 from PB.go.chromium.org.luci.resultdb.proto.v1 import test_result as test_result_pb2
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'context',
     'json',
diff --git a/recipe_modules/resultdb/examples/include.expected/basic.json b/recipe_modules/resultdb/examples/include.expected/basic.json
index 86b5957..1806780 100644
--- a/recipe_modules/resultdb/examples/include.expected/basic.json
+++ b/recipe_modules/resultdb/examples/include.expected/basic.json
@@ -24,8 +24,8 @@
     },
     "name": "rdb query",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@raw_io.output@{\"invocation\": {\"state\": \"FINALIZED\"}, \"invocationId\": \"invid2\"}@@@",
       "@@@STEP_LOG_LINE@raw_io.output@{\"invocation\": {\"state\": \"FINALIZED\"}, \"invocationId\": \"invid\"}@@@",
+      "@@@STEP_LOG_LINE@raw_io.output@{\"invocation\": {\"state\": \"FINALIZED\"}, \"invocationId\": \"invid2\"}@@@",
       "@@@STEP_LOG_END@raw_io.output@@@"
     ]
   },
@@ -51,14 +51,14 @@
       }
     },
     "name": "rdb include",
-    "stdin": "{\"addInvocations\": [\"invocations/invid2\", \"invocations/invid\"], \"includingInvocation\": \"invocations/build:8945511751514863184\"}",
+    "stdin": "{\"addInvocations\": [\"invocations/invid\", \"invocations/invid2\"], \"includingInvocation\": \"invocations/build:8945511751514863184\"}",
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@json.output@{}@@@",
       "@@@STEP_LOG_END@json.output@@@",
       "@@@STEP_LOG_LINE@json.input@{@@@",
       "@@@STEP_LOG_LINE@json.input@  \"addInvocations\": [@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid2\", @@@",
-      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid\", @@@",
+      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid2\"@@@",
       "@@@STEP_LOG_LINE@json.input@  ], @@@",
       "@@@STEP_LOG_LINE@json.input@  \"includingInvocation\": \"invocations/build:8945511751514863184\"@@@",
       "@@@STEP_LOG_LINE@json.input@}@@@",
@@ -87,15 +87,15 @@
       }
     },
     "name": "rdb exclude",
-    "stdin": "{\"includingInvocation\": \"invocations/build:8945511751514863184\", \"removeInvocations\": [\"invocations/invid2\", \"invocations/invid\"]}",
+    "stdin": "{\"includingInvocation\": \"invocations/build:8945511751514863184\", \"removeInvocations\": [\"invocations/invid\", \"invocations/invid2\"]}",
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@json.output@{}@@@",
       "@@@STEP_LOG_END@json.output@@@",
       "@@@STEP_LOG_LINE@json.input@{@@@",
       "@@@STEP_LOG_LINE@json.input@  \"includingInvocation\": \"invocations/build:8945511751514863184\", @@@",
       "@@@STEP_LOG_LINE@json.input@  \"removeInvocations\": [@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid2\", @@@",
-      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid\", @@@",
+      "@@@STEP_LOG_LINE@json.input@    \"invocations/invid2\"@@@",
       "@@@STEP_LOG_LINE@json.input@  ]@@@",
       "@@@STEP_LOG_LINE@json.input@}@@@",
       "@@@STEP_LOG_END@json.input@@@"
diff --git a/recipe_modules/resultdb/examples/include.py b/recipe_modules/resultdb/examples/include.py
index 2671a4d..a82e260 100644
--- a/recipe_modules/resultdb/examples/include.py
+++ b/recipe_modules/resultdb/examples/include.py
@@ -8,6 +8,8 @@
 from PB.go.chromium.org.luci.lucictx import sections as sections_pb2
 from PB.go.chromium.org.luci.resultdb.proto.v1 import invocation as invocation_pb2
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
   'context',
   'resultdb',
@@ -20,7 +22,7 @@
     step_name='rdb query',
     variants_with_unexpected_results=True,
   )
-  invocation_ids = inv_bundle.keys()
+  invocation_ids = sorted(inv_bundle)
   api.resultdb.include_invocations(invocation_ids, step_name='rdb include')
   api.resultdb.exclude_invocations(invocation_ids, step_name='rdb exclude')
 
diff --git a/recipe_modules/resultdb/examples/query.py b/recipe_modules/resultdb/examples/query.py
index 8b5574d..2c87d89 100644
--- a/recipe_modules/resultdb/examples/query.py
+++ b/recipe_modules/resultdb/examples/query.py
@@ -12,6 +12,8 @@
 from PB.go.chromium.org.luci.resultdb.proto.v1 import invocation as invocation_pb2
 from PB.go.chromium.org.luci.resultdb.proto.v1 import test_result as test_result_pb2
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
   'buildbucket',
   'resultdb',
diff --git a/recipe_modules/resultdb/examples/query_test_result_statistics.py b/recipe_modules/resultdb/examples/query_test_result_statistics.py
index 50fd1ab..538e493 100644
--- a/recipe_modules/resultdb/examples/query_test_result_statistics.py
+++ b/recipe_modules/resultdb/examples/query_test_result_statistics.py
@@ -7,6 +7,8 @@
 from PB.go.chromium.org.luci.lucictx import sections as sections_pb2
 from PB.go.chromium.org.luci.resultdb.proto.v1 import resultdb
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'context',
     'resultdb',
diff --git a/recipe_modules/resultdb/examples/result_history.py b/recipe_modules/resultdb/examples/result_history.py
index 9063bdf..b077534 100644
--- a/recipe_modules/resultdb/examples/result_history.py
+++ b/recipe_modules/resultdb/examples/result_history.py
@@ -8,6 +8,8 @@
 from PB.go.chromium.org.luci.resultdb.proto.v1 import resultdb
 from PB.go.chromium.org.luci.resultdb.proto.v1 import test_result as test_result_pb2
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'resultdb',
 ]
diff --git a/recipe_modules/resultdb/examples/resultsink.py b/recipe_modules/resultdb/examples/resultsink.py
index c951c26..01807e7 100644
--- a/recipe_modules/resultdb/examples/resultsink.py
+++ b/recipe_modules/resultdb/examples/resultsink.py
@@ -4,6 +4,8 @@
 
 from PB.go.chromium.org.luci.lucictx import sections as sections_pb2
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
   'context',
   'resultdb',
diff --git a/recipe_modules/resultdb/examples/test_presentation.py b/recipe_modules/resultdb/examples/test_presentation.py
index 5e43437..756e568 100644
--- a/recipe_modules/resultdb/examples/test_presentation.py
+++ b/recipe_modules/resultdb/examples/test_presentation.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed under the Apache License, Version 2.0
 # that can be found in the LICENSE file.
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'resultdb',
 ]
diff --git a/recipe_modules/resultdb/examples/test_presentation_default.py b/recipe_modules/resultdb/examples/test_presentation_default.py
index 690f7cc..6bf045b 100644
--- a/recipe_modules/resultdb/examples/test_presentation_default.py
+++ b/recipe_modules/resultdb/examples/test_presentation_default.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed under the Apache License, Version 2.0
 # that can be found in the LICENSE file.
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'resultdb',
 ]
diff --git a/recipe_modules/resultdb/examples/upload_invocation_artifacts.py b/recipe_modules/resultdb/examples/upload_invocation_artifacts.py
index 549a821..d808d0e 100644
--- a/recipe_modules/resultdb/examples/upload_invocation_artifacts.py
+++ b/recipe_modules/resultdb/examples/upload_invocation_artifacts.py
@@ -7,6 +7,8 @@
 from PB.go.chromium.org.luci.resultdb.proto.v1 import artifact
 from PB.go.chromium.org.luci.resultdb.proto.v1 import recorder
 
+PYTHON_VERSION_COMPATIBILITY = "PY2+3"
+
 DEPS = [
     'resultdb',
 ]