From 1be8abfbf9cf0bbee7ed7b312e8a0376dfaa98e4 Mon Sep 17 00:00:00 2001
From: "M.P. Korstanje" <rien.korstanje@gmail.com>
Date: Sat, 23 Mar 2024 18:08:01 +0100
Subject: [PATCH] java: include stacktrace in Convertor.toMessage(Throwable)

---
 CHANGELOG.md                                  |  2 ++
 java/pom.xml                                  |  8 ++++++
 .../java/io/cucumber/messages/Convertor.java  | 25 +++++++++++++++++--
 .../io/cucumber/messages/ConvertorTest.java   | 22 +++++++++++++---
 4 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e22dbcdfb..4119c665d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
+### Added 
+-  java: include stacktrace in Convertor.toMessage(Throwable) ([#213](https://github.com/cucumber/messages/pull/213))
 
 ## [24.0.1] - 2023-12-21
 ### Fixed
diff --git a/java/pom.xml b/java/pom.xml
index e09223e95..dc053f98e 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -87,6 +87,14 @@
             </resource>
         </resources>
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <configuration>
+                    <propertiesEncoding>UTF-8</propertiesEncoding>
+                </configuration>
+            </plugin>
+
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
diff --git a/java/src/main/java/io/cucumber/messages/Convertor.java b/java/src/main/java/io/cucumber/messages/Convertor.java
index a16771385..4f995b716 100644
--- a/java/src/main/java/io/cucumber/messages/Convertor.java
+++ b/java/src/main/java/io/cucumber/messages/Convertor.java
@@ -4,29 +4,50 @@
 import io.cucumber.messages.types.Exception;
 import io.cucumber.messages.types.Timestamp;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+
+import static java.util.Objects.requireNonNull;
+
 public final class Convertor {
 
     private Convertor(){
 
     }
 
-    public static Exception toMessage(Throwable t) {
-        return new Exception(t.getClass().getName(), t.getMessage(), null);
+    public static Exception toMessage(Throwable throwable) {
+        requireNonNull(throwable, "throwable may not be null");
+        return new Exception(throwable.getClass().getName(), throwable.getMessage(), extractStackTrace(throwable));
+    }
+
+    private static String extractStackTrace(Throwable throwable) {
+        StringWriter stringWriter = new StringWriter();
+        try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
+            throwable.printStackTrace(printWriter);
+        }
+        return stringWriter.toString();
     }
 
     public static Timestamp toMessage(java.time.Instant instant) {
+        requireNonNull(instant, "instant may not be null");
         return new Timestamp(instant.getEpochSecond(), (long) instant.getNano());
     }
 
     public static Duration toMessage(java.time.Duration duration) {
+        requireNonNull(duration, "duration may not be null");
         return new Duration(duration.getSeconds(), (long) duration.getNano());
     }
 
     public static java.time.Instant toInstant(Timestamp timestamp) {
+        requireNonNull(timestamp, "timestamp may not be null");
         return java.time.Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
     }
 
     public static java.time.Duration toDuration(Duration duration) {
+        requireNonNull(duration, "duration may not be null");
         return java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos());
     }
 
diff --git a/java/src/test/java/io/cucumber/messages/ConvertorTest.java b/java/src/test/java/io/cucumber/messages/ConvertorTest.java
index f22e4bc78..b2fec86f9 100644
--- a/java/src/test/java/io/cucumber/messages/ConvertorTest.java
+++ b/java/src/test/java/io/cucumber/messages/ConvertorTest.java
@@ -3,10 +3,14 @@
 import io.cucumber.messages.types.Duration;
 import io.cucumber.messages.types.Exception;
 import io.cucumber.messages.types.Timestamp;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.jupiter.api.Test;
 
 import java.util.Optional;
 
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -14,13 +18,25 @@ class ConvertorTest {
 
     @Test
     void convertsExceptionToMessage() {
+        Exception e = Convertor.toMessage(new RuntimeException());
+        assertAll(
+                () -> assertEquals(Optional.empty(), e.getMessage()),
+                () -> assertEquals("java.lang.RuntimeException", e.getType()),
+                () -> assertThat(e.getStackTrace().get(), startsWith("" +
+                        "java.lang.RuntimeException\n" +
+                        "\tat io.cucumber.messages.ConvertorTest.convertsExceptionToMessage(ConvertorTest.java:"))
+        );
+    }
+
+    @Test
+    void convertsExceptionWithMessageToMessage() {
         Exception e = Convertor.toMessage(new RuntimeException("Hello world!"));
-        Exception e2 = Convertor.toMessage(new RuntimeException());
         assertAll(
                 () -> assertEquals(Optional.of("Hello world!"), e.getMessage()),
-                () -> assertEquals(Optional.empty(), e2.getMessage()),
                 () -> assertEquals("java.lang.RuntimeException", e.getType()),
-                () -> assertEquals("java.lang.RuntimeException", e2.getType())
+                () -> assertThat(e.getStackTrace().get(), startsWith("" +
+                        "java.lang.RuntimeException: Hello world!\n" +
+                        "\tat io.cucumber.messages.ConvertorTest.convertsExceptionWithMessageToMessage(ConvertorTest.java:"))
         );
     }