Skip to content

Commit 3546dcd

Browse files
authored
Merge pull request #17 from virtualcell/errorMessageImprovement
Add generateErrorReport for better error messages (0.0.16)
2 parents ed404a2 + 6ac059c commit 3546dcd

4 files changed

Lines changed: 1842 additions & 1 deletion

File tree

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "libvcell"
7-
version = "0.0.15.4"
7+
version = "0.0.16"
88
description = "This is a python package which wraps a subset of VCell Java code as a native python package."
99
authors = ["Jim Schaff <schaff@uchc.edu>", "Ezequiel Valencia <evalencia@uchc.edu>"]
1010
repository = "https://github.com/virtualcell/libvcell"

‎vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
import java.io.File;
1616
import java.nio.file.Path;
17+
import java.util.List;
18+
import java.util.ArrayList;
19+
import java.util.Map;
20+
import java.util.LinkedHashMap;
21+
import java.util.Set;
22+
import java.util.Arrays;
23+
import java.util.stream.Collectors;
1724
import java.util.concurrent.ConcurrentHashMap;
1825

1926
import static org.vcell.libvcell.ModelUtils.*;
@@ -34,6 +41,47 @@ private static CCharPointer createString(String str) {
3441
return ptr;
3542
}
3643

44+
// should only be externally invoked by tests!
45+
public static String generateErrorReport(String topMostMessage, Throwable exceptionEncountered){
46+
boolean hasNoMessage = topMostMessage == null;
47+
boolean hasNoException = exceptionEncountered == null;
48+
if (hasNoMessage && hasNoException) throw new IllegalArgumentException("Both arguments cannot be null");
49+
if (hasNoException) return topMostMessage;
50+
StringBuilder errorMessage = new StringBuilder();
51+
errorMessage.append(hasNoMessage ? "VCell encountered the following error" : topMostMessage).append("\n");
52+
53+
List<String> exceptionTypes = new ArrayList<>();
54+
List<String> stackMessages = new ArrayList<>();
55+
List<StackTraceElement[]> stackTraces = new ArrayList<>();
56+
Map<StackTraceElement[], Set<StackTraceElement>> stackTraceMapping = new LinkedHashMap<>();
57+
Throwable cause = exceptionEncountered;
58+
do {
59+
exceptionTypes.add(cause.getClass().getSimpleName());
60+
stackMessages.add(cause.getMessage());
61+
StackTraceElement[] stack = cause.getStackTrace();
62+
stackTraces.add(stack);
63+
stackTraceMapping.put(stack, Arrays.stream(stack).collect(Collectors.toSet()));
64+
} while (null != (cause = cause.getCause()));
65+
66+
errorMessage.append("Error:\n");
67+
for (int i = 0; i < exceptionTypes.size(); i++) {
68+
errorMessage.append(i).append(" ").repeat("-", 3 * (i + 1) - 1).append("> ");
69+
errorMessage.append(exceptionTypes.get(i)).append(" :: ").append(stackMessages.get(i)).append("\n");
70+
}
71+
errorMessage.append("\nStack Traces:\n");
72+
for (int i = exceptionTypes.size() - 1; i >= 0; i--) {
73+
errorMessage.append(i).append(") ").append(exceptionTypes.get(i)).append(":\n");
74+
StackTraceElement[] stack = stackTraces.get(i);
75+
Set<StackTraceElement> oldStackTraceSet = i > 0 ? stackTraceMapping.get(stackTraces.get(i - 1)): Set.of();
76+
for (StackTraceElement ste : stack) {
77+
if (oldStackTraceSet.contains(ste)) break;
78+
errorMessage.append(ste).append("\n");
79+
}
80+
errorMessage.append("\n");
81+
}
82+
return errorMessage.toString();
83+
}
84+
3785

3886
@CEntryPoint(name = "freeString", documentation = "Release memory allocated for a string")
3987
public static void freeString(
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.vcell.libvcell;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.nio.charset.StandardCharsets;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
11+
import static org.vcell.libvcell.ModelUtils.vcml_to_vcml;
12+
13+
public class MiscTests {
14+
15+
@Test
16+
public void testErrorMessageCreation() throws IOException {
17+
String vcmlContent;
18+
try (InputStream is = MiscTests.class.getResourceAsStream("/bad_vcml.vcml")) {
19+
Assertions.assertNotNull(is);
20+
vcmlContent = new String(is.readAllBytes(), StandardCharsets.UTF_8);
21+
}
22+
Path ignored = Files.createTempFile("dummy", ".vcml");
23+
try {
24+
vcml_to_vcml(vcmlContent, ignored);
25+
} catch (Exception e) {
26+
String actualErrMsg = Entrypoints.generateErrorReport("Something went wrong!", e).strip();
27+
28+
// The report opens with the caller-supplied top-level message, followed by a structured
29+
// summary of the nested cause chain. These parts are deterministic, so assert them exactly.
30+
// We deliberately do NOT assert the verbatim stack-trace frames: they embed vcell-core line
31+
// numbers (which drift as the submodule updates) and test-runner frames (which differ between
32+
// IDE and Maven Surefire), making a full-string comparison inherently brittle.
33+
Assertions.assertTrue(actualErrMsg.startsWith(EXPECTED_SUMMARY), actualErrMsg);
34+
35+
// A stack-trace section is appended, deepest cause first, listing real frames from the failure.
36+
Assertions.assertTrue(actualErrMsg.contains("Stack Traces:"), actualErrMsg);
37+
Assertions.assertTrue(actualErrMsg.contains("1) ExpressionBindingException:"), actualErrMsg);
38+
Assertions.assertTrue(actualErrMsg.contains("0) XmlParseException:"), actualErrMsg);
39+
Assertions.assertTrue(actualErrMsg.contains("cbit.vcell.parser.ASTIdNode.bind("), actualErrMsg);
40+
Assertions.assertTrue(actualErrMsg.contains("cbit.vcell.xml.XmlReader.getSpeciesContextSpecs("), actualErrMsg);
41+
Assertions.assertTrue(actualErrMsg.contains("org.vcell.libvcell.ModelUtils.vcml_to_vcml("), actualErrMsg);
42+
return;
43+
}
44+
throw new IllegalStateException("An exception was not thrown when one should have been!");
45+
}
46+
47+
private static final String EXPECTED_SUMMARY = """
48+
Something went wrong!
49+
Error:
50+
0 --> XmlParseException :: Error setting Velocity parameter for 'H3
51+
1 -----> ExpressionBindingException :: 'norm_X' is either not found in your model or is not allowed to be used in the current context. Check that you have provided the correct and full name (e.g. Ca_Cytosol).""".strip();
52+
53+
}

0 commit comments

Comments
 (0)