Repository: ontodev/robot
Pull request #1022 (2022-06-28 14:43:38)

[#1016] Fixes JSON report serialisation
Resolves [#1016]

- [x] `docs/` have been added/updated
- [x] tests have been added/updated
- [x] `mvn verify` says all tests pass
- [x] `mvn site` says all JavaDocs correct
- [x] `CHANGELOG.md` has been updated

Actually the added tests are maybe more valuable then the resolution itself - since I do not know what was the rationale behind the way the double-quotes in YAML serialisation were escaped before I honestly do not know what I have broken by this change - just hope that not too much :-) ...


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 018ac703..d392f7f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Add "domain" and "range" support to export [#1061]
 
 ### Fixed
+- Fixed report serialization in JSON [#1016]
 - Fix missing labels in [`diff`] output. [#1026]
 
 ## [1.9.0] - 2022-06-16
@@ -329,6 +330,7 @@ First official release of ROBOT!
 [#1061]: https://github.com/ontodev/robot/issues/1061
 [#1026]: https://github.com/ontodev/robot/issues/1026
 [#1017]: https://github.com/ontodev/robot/issues/1017
+[#1016]: https://github.com/ontodev/robot/issues/1016
 [#1009]: https://github.com/ontodev/robot/issues/1009
 [#979]: https://github.com/ontodev/robot/pull/979
 [#978]: https://github.com/ontodev/robot/pull/978
diff --git a/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java b/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java
index 943f842e..ca3f30ff 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java
@@ -5,9 +5,13 @@ import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.*;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.stream.Collectors;
 import org.apache.commons.io.FileUtils;
 import org.apache.jena.query.*;
 import org.apache.jena.tdb.TDBFactory;
@@ -695,20 +699,21 @@ public class ReportOperation {
     Map<String, String> queries = new HashMap<>();
     // Handle simple file path, probably accessed during testing
     if (dirURL != null && dirURL.getProtocol().equals("file")) {
-      String[] queryFilePaths = new File(dirURL.toURI()).list();
-      if (queryFilePaths == null || queryFilePaths.length == 0) {
+      final Set<Path> queryFilePaths =
+          Files.list(Paths.get(dirURL.toURI())).collect(Collectors.toSet());
+      if (queryFilePaths.size() == 0) {
         throw new IOException(
             "Cannot access report query files. There are no files in the directory.");
       }
       Set<String> defaultRuleNames = new HashSet<>();
-      for (String qPath : queryFilePaths) {
-        String ruleName = qPath.substring(qPath.lastIndexOf("/")).split(".")[0];
+      for (final Path qPath : queryFilePaths) {
+        final String ruleName = qPath.getFileName().toString().split("[.]")[0];
         defaultRuleNames.add(ruleName);
         // Only add it to the queries if the rule set contains that rule
         // If rules == null, include all rules
         if (rules == null || rules.contains(ruleName)) {
           queries.put(
-              ruleName, FileUtils.readFileToString(new File(qPath), Charset.defaultCharset()));
+              ruleName, FileUtils.readFileToString(qPath.toFile(), Charset.defaultCharset()));
         }
       }
       // Check that all the provided rule names are valid defaults
diff --git a/robot-core/src/main/java/org/obolibrary/robot/checks/Report.java b/robot-core/src/main/java/org/obolibrary/robot/checks/Report.java
index 60134760..8165b9f4 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/checks/Report.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/checks/Report.java
@@ -556,7 +556,7 @@ public class Report {
               display =
                   OntologyHelper.renderManchester(value, provider, RendererType.OBJECT_RENDERER);
             }
-            sb.append("        - \"").append(display.replace("\"", "\\\"")).append("\"");
+            sb.append("        - \"").append(display).append("\"");
             sb.append("\n");
           }
         }
@@ -576,7 +576,7 @@ public class Report {
             if (value == null) {
               value = "";
             }
-            sb.append("        - \"").append(value.replace("\"", "\\\"")).append("\"");
+            sb.append("        - \"").append(value).append("\"");
             sb.append("\n");
           }
         }


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/ReportOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/ReportOperationTest.java
new file mode 100644
index 00000000..2dc03531
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/ReportOperationTest.java
@@ -0,0 +1,53 @@
+package org.obolibrary.robot;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.yaml.snakeyaml.error.YAMLException;
+
+/**
+ * Tests for the ReportOperation.
+ */
+public class ReportOperationTest extends CoreTest {
+
+  /**
+   * Test report produces correct JSON.
+   */
+  @Test
+  public void testReportProducesValidJson() throws Exception {
+    testReportProducesCorrectOutput("json");
+  }
+
+  /**
+   * Test report produces correct YAML.
+   */
+  @Test
+  public void testReportProducesValidYaml() throws Exception {
+    testReportProducesCorrectOutput("yaml");
+  }
+
+  private void testReportProducesCorrectOutput(String extension) throws Exception {
+    try {
+      final OWLOntology ontology = loadOntology("/1016-report-json-failure/input.owl");
+      final IOHelper iohelper = new IOHelper();
+      final File outputFile =
+        File.createTempFile("1016-report-json-failure-output", "." + extension);
+      ReportOperation.report(ontology, iohelper, outputFile.toString(), Collections.emptyMap());
+      final String output = IOUtils.toString(new FileInputStream(outputFile), Charset.defaultCharset()).trim();
+      final InputStream expected = getClass().getResourceAsStream("/1016-report-json-failure/output." + extension);
+      assert expected != null;
+      final String expectedOutput =
+        IOUtils.toString(expected, StandardCharsets.UTF_8.name()).trim();
+      Assert.assertEquals(expectedOutput, output);
+    } catch (YAMLException e) {
+      e.printStackTrace();
+      Assert.fail();
+    }
+  }
+}
diff --git a/robot-core/src/test/resources/1016-report-json-failure/input.owl b/robot-core/src/test/resources/1016-report-json-failure/input.owl
new file mode 100644
index 00000000..2e5a6e85
--- /dev/null
+++ b/robot-core/src/test/resources/1016-report-json-failure/input.owl
@@ -0,0 +1,11 @@
+<rdf:RDF
+     xmlns:obo="http://purl.obolibrary.org/obo/"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:xml="http://www.w3.org/XML/1998/namespace">
+    <owl:Ontology rdf:about="http://test.org/test"/>
+
+    <owl:ObjectProperty rdf:about="http://test.org/test#A">
+        <obo:IAO_0000115>skos:Concept interferes with &quot;quoted text&quot;</obo:IAO_0000115>
+    </owl:ObjectProperty>
+</rdf:RDF>
\ No newline at end of file
diff --git a/robot-core/src/test/resources/1016-report-json-failure/output.json b/robot-core/src/test/resources/1016-report-json-failure/output.json
new file mode 100644
index 00000000..c7da3a95
--- /dev/null
+++ b/robot-core/src/test/resources/1016-report-json-failure/output.json
@@ -0,0 +1,54 @@
+[
+  {
+    "level": "ERROR",
+    "violations": [
+      {
+        "missing_label": [
+          {
+            "subject": "http://test.org/test#A"
+          }
+        ]
+      },
+      {
+        "missing_ontology_description": [
+          {
+            "subject": "http://test.org/test"
+          }
+        ]
+      },
+      {
+        "missing_ontology_license": [
+          {
+            "subject": "http://test.org/test"
+          }
+        ]
+      },
+      {
+        "missing_ontology_title": [
+          {
+            "subject": "http://test.org/test"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "level": "WARN"
+  },
+  {
+    "level": "INFO",
+    "violations": [
+      {
+        "lowercase_definition": [
+          {
+            "subject": "http://test.org/test#A",
+            "property": "IAO:0000115",
+            "values": [
+              "skos:Concept interferes with \"quoted text\""
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/robot-core/src/test/resources/1016-report-json-failure/output.yaml b/robot-core/src/test/resources/1016-report-json-failure/output.yaml
new file mode 100644
index 00000000..bfc84da2
--- /dev/null
+++ b/robot-core/src/test/resources/1016-report-json-failure/output.yaml
@@ -0,0 +1,19 @@
+- level: 'ERROR'
+  violations:
+  - missing_label:
+    - subject: "http://test.org/test#A"
+  - missing_ontology_description:
+    - subject: "http://test.org/test"
+  - missing_ontology_license:
+    - subject: "http://test.org/test"
+  - missing_ontology_title:
+    - subject: "http://test.org/test"
+- level: 'WARN'
+  violations:
+- level: 'INFO'
+  violations:
+  - lowercase_definition:
+    - subject: "http://test.org/test#A"
+      property: "IAO:0000115"
+      values:
+        - "skos:Concept interferes with \"quoted text\""