Revert^2 "Send test results to result sink"

This reverts commit 8d2fd08e4f9736c164c6b06b53d4c3fe34d23035.

Change-Id: I6d07076a70d8700dd4b70319e159d4d6a22d1ece
diff --git a/d8_r8/commonBuildSrc/build.gradle.kts b/d8_r8/commonBuildSrc/build.gradle.kts
index 5f32acf..c328da9 100644
--- a/d8_r8/commonBuildSrc/build.gradle.kts
+++ b/d8_r8/commonBuildSrc/build.gradle.kts
@@ -7,7 +7,10 @@
   `java-gradle-plugin`
 }
 
-dependencies { implementation("net.ltgt.gradle:gradle-errorprone-plugin:3.0.1") }
+dependencies {
+  implementation("net.ltgt.gradle:gradle-errorprone-plugin:3.0.1")
+  implementation("com.google.code.gson:gson:2.10.1")
+}
 
 gradlePlugin {
   plugins.register("dependencies-plugin") {
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index 875da87..38f978b 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -2,12 +2,19 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import com.google.gson.Gson
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.PrintStream
+import java.net.HttpURLConnection
+import java.net.URL
 import java.nio.charset.StandardCharsets
 import java.util.Date
 import java.util.concurrent.TimeUnit
+import kotlin.io.encoding.Base64
+import kotlin.io.encoding.ExperimentalEncodingApi
 import org.gradle.api.tasks.testing.Test
 import org.gradle.api.tasks.testing.TestDescriptor
 import org.gradle.api.tasks.testing.TestListener
@@ -17,6 +24,34 @@
 
   public companion object {
 
+    private data class ResultSinkInfo(val address: String, val authToken: String)
+
+    private val resultSinkInfo: ResultSinkInfo? by lazy {
+      val luciContextPath = System.getenv("LUCI_CONTEXT")
+      if (luciContextPath != null) {
+        try {
+          val content = File(luciContextPath).readText()
+          val json = Gson().fromJson(content, JsonObject::class.java)
+          val resultSink = json.getAsJsonObject("result_sink")
+          if (resultSink != null) {
+            val address = resultSink.get("address")?.asString
+            val authToken = resultSink.get("auth_token")?.asString
+            if (address != null && authToken != null) {
+              ResultSinkInfo(address, authToken)
+            } else {
+              null
+            }
+          } else {
+            null
+          }
+        } catch (e: Exception) {
+          null
+        }
+      } else {
+        null
+      }
+    }
+
     private fun retrace(
       rootDir: File,
       r8jar: File,
@@ -61,6 +96,98 @@
       return out.toString()
     }
 
+    @OptIn(ExperimentalEncodingApi::class)
+    private fun reportToResultSink(
+      desc: TestDescriptor?,
+      result: TestResult?,
+      rootDir: File,
+      isR8Lib: Boolean,
+      r8Jar: File?,
+      r8LibMappingFile: File?,
+      printObfuscatedStacktraces: Boolean,
+    ) {
+      val info = resultSinkInfo ?: return
+      if (desc == null || result == null || desc.className == null) return
+
+      val testId = "${desc.className}.${desc.name}"
+      val status =
+        when (result.resultType) {
+          TestResult.ResultType.SUCCESS -> "PASS"
+          TestResult.ResultType.FAILURE -> "FAIL"
+          TestResult.ResultType.SKIPPED -> "SKIP"
+          else -> "FAIL"
+        }
+      val expected = result.resultType == TestResult.ResultType.SUCCESS
+      val durationMs = result.endTime - result.startTime
+      val duration = "${durationMs / 1000.0}s"
+
+      var stackTraceStr: String? = null
+      if (result.resultType == TestResult.ResultType.FAILURE && result.exception != null) {
+        val exception = result.exception as Throwable
+        stackTraceStr =
+          if (isR8Lib && r8Jar != null && r8LibMappingFile != null) {
+            retrace(rootDir, r8Jar, r8LibMappingFile, exception, printObfuscatedStacktraces)
+          } else {
+            val baos = ByteArrayOutputStream()
+            exception.printStackTrace(PrintStream(baos, true, StandardCharsets.UTF_8))
+            baos.toString()
+          }
+      }
+
+      val payloadObj = JsonObject()
+      val testResultsArr = com.google.gson.JsonArray()
+      val testResultObj = JsonObject()
+
+      val testIdStructuredObj = JsonObject()
+      val testIdCaseNameComponents = JsonArray()
+      testIdCaseNameComponents.add(desc.displayName.substringBeforeLast('['))
+      val argumentString = desc.displayName.substringAfterLast('[').substringBeforeLast(']')
+      testIdStructuredObj.addProperty("module", "junit")
+      testIdStructuredObj.addProperty("coarseName", desc.className?.substringBeforeLast(".") ?: "")
+      testIdStructuredObj.addProperty("fineName", desc.className?.substringAfterLast(".") ?: "")
+      testIdStructuredObj.add("caseNameComponents", testIdCaseNameComponents)
+      testResultObj.add("testIdStructured", testIdStructuredObj)
+
+      testResultObj.addProperty("testId", testId)
+      testResultObj.addProperty("status", status)
+      testResultObj.addProperty("expected", expected)
+      testResultObj.addProperty("duration", duration)
+      val summaryHtml = "<p>Arguments string: $argumentString</p>"
+      if (stackTraceStr != null) {
+        val artifact = JsonObject()
+        val encodedStackTrace = Base64.Default.encode(stackTraceStr.encodeToByteArray())
+        val jsonObject = JsonObject()
+        jsonObject.addProperty("contents", encodedStackTrace)
+        artifact.add("artifact-content-in-request", jsonObject)
+        testResultObj.add("artifacts", artifact)
+        testResultObj.addProperty(
+          "summaryHtml",
+          summaryHtml +
+            "<p>Stack trace:</p>" +
+            "<p><text-artifact artifact-id=\"artifact-content-in-request\"></p>",
+        )
+      } else {
+        testResultObj.addProperty("summaryHtml", summaryHtml)
+      }
+      testResultsArr.add(testResultObj)
+      payloadObj.add("testResults", testResultsArr)
+
+      try {
+        val url = URL("http://${info.address}/prpc/luci.resultsink.v1.Sink/ReportTestResults")
+        val conn = url.openConnection() as HttpURLConnection
+        conn.requestMethod = "POST"
+        conn.doOutput = true
+        conn.setRequestProperty("Content-Type", "application/json")
+        conn.setRequestProperty("Authorization", "ResultSink ${info.authToken}")
+        conn.outputStream.use {
+          it.write(Gson().toJson(payloadObj).toByteArray(StandardCharsets.UTF_8))
+        }
+        conn.inputStream.use { it.readBytes() }
+      } catch (e: Exception) {
+        // Ignore
+      }
+    }
+
     public fun setupTestTask(test: Test, isR8Lib: Boolean, r8Jar: File?, r8LibMappingFile: File?) {
       // TODO(b/489058560) Enable when we have figured out re-running single test variants.
       // test.useJUnitPlatform()
@@ -213,6 +340,15 @@
                   println(baos)
                 }
               }
+              reportToResultSink(
+                desc,
+                result,
+                rootDir,
+                isR8Lib,
+                r8Jar,
+                r8LibMappingFile,
+                printObfuscatedStacktraces,
+              )
             }
           }
         )
@@ -233,6 +369,15 @@
                 exception.printStackTrace(PrintStream(baos, true, StandardCharsets.UTF_8))
                 println(baos)
               }
+              reportToResultSink(
+                desc,
+                result,
+                rootDir,
+                isR8Lib,
+                r8Jar,
+                r8LibMappingFile,
+                printObfuscatedStacktraces,
+              )
             }
           }
         )