aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-testing-harness/src/main/java/org/sonar/test
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-03-19 14:46:58 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-03-20 11:07:24 +0100
commit88219ea163c159be7d16203be32d8f0b3d7e7d0a (patch)
treeef96d6c0860ebac6b53ff6f39d521a733c14c25b /sonar-testing-harness/src/main/java/org/sonar/test
parent1af9a73913f04992e8a9033b29980dd74c6f0a22 (diff)
downloadsonarqube-88219ea163c159be7d16203be32d8f0b3d7e7d0a.tar.gz
sonarqube-88219ea163c159be7d16203be32d8f0b3d7e7d0a.zip
Replace lib org.skyscreamer.jsonassert by org.sonar.test.JsonAssert.
This class supports lenient comparison of datetime values.
Diffstat (limited to 'sonar-testing-harness/src/main/java/org/sonar/test')
-rw-r--r--sonar-testing-harness/src/main/java/org/sonar/test/JsonAssert.java100
-rw-r--r--sonar-testing-harness/src/main/java/org/sonar/test/JsonComparison.java198
2 files changed, 298 insertions, 0 deletions
diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/JsonAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/JsonAssert.java
new file mode 100644
index 00000000000..ac9f2470da6
--- /dev/null
+++ b/sonar-testing-harness/src/main/java/org/sonar/test/JsonAssert.java
@@ -0,0 +1,100 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.test;
+
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.junit.ComparisonFailure;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Assertion to compare JSON documents. Comparison is not strict:
+ * <ul>
+ * <li>formatting differences are ignored</li>
+ * <li>order of elements in objects <code>{}</code> is not verified</li>
+ * <li>objects can contain more elements than expected, for example <code>{"one":1, "two":2}</code>
+ * matches <code>{"one":1}</code></li>
+ * <li>order of elements in arrays <code>[]</code> is not verified by default, for example <code>[1, 2]</code>
+ * matches <code>[2, 1]</code>. This mode can be disabled with {@link #setStrictArrayOrder(boolean)}</li>
+ * <li>timezones in datetime values are not strictly verified, for example <code>{"foo": "2015-01-01T13:00:00+2000"}</code>
+ * matches <code>{"foo": "2015-01-01T10:00:00-1000"}</code>. This feature can be disabled with
+ * {@link #setStrictTimezone(boolean)}
+ * </li>
+ * </ul>
+ *
+ * @since 5.2
+ */
+public class JsonAssert {
+
+ private final String actualJson;
+ private final JsonComparison comparison = new JsonComparison();
+
+ private JsonAssert(String actualJson) {
+ this.actualJson = actualJson;
+ }
+
+ public JsonAssert setStrictTimezone(boolean b) {
+ comparison.setStrictTimezone(b);
+ return this;
+ }
+
+ public JsonAssert setStrictArrayOrder(boolean b) {
+ comparison.setStrictArrayOrder(b);
+ return this;
+ }
+
+ public JsonAssert isSimilarTo(String expected) {
+ boolean similar = comparison.areSimilar(expected, actualJson);
+ if (!similar) {
+ throw new ComparisonFailure("Not a super-set of expected JSON -", pretty(expected), pretty(actualJson));
+ }
+ return this;
+ }
+
+ public JsonAssert isSimilarTo(URL expected) {
+ return isSimilarTo(urlToString(expected));
+ }
+
+ public static JsonAssert assertJson(String actualJson) {
+ return new JsonAssert(actualJson);
+ }
+
+ public static JsonAssert assertJson(URL actualJson) {
+ return new JsonAssert(urlToString(actualJson));
+ }
+
+ private static String urlToString(URL url) {
+ try {
+ return IOUtils.toString(url, Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to load JSON from " + url, e);
+ }
+ }
+
+ private static String pretty(String json) {
+ JsonElement gson = new JsonParser().parse(json);
+ return new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(gson);
+ }
+}
diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/JsonComparison.java b/sonar-testing-harness/src/main/java/org/sonar/test/JsonComparison.java
new file mode 100644
index 00000000000..1a437f5daad
--- /dev/null
+++ b/sonar-testing-harness/src/main/java/org/sonar/test/JsonComparison.java
@@ -0,0 +1,198 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.test;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Not thread-safe because of field datetimeFormat which is SimpleDateFormat.
+ */
+class JsonComparison {
+
+ private static final SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+
+ private boolean strictTimezone = false;
+ private boolean strictArrayOrder = false;
+
+ boolean isStrictTimezone() {
+ return strictTimezone;
+ }
+
+ JsonComparison setStrictTimezone(boolean b) {
+ this.strictTimezone = b;
+ return this;
+ }
+
+ boolean isStrictArrayOrder() {
+ return strictArrayOrder;
+ }
+
+ JsonComparison setStrictArrayOrder(boolean b) {
+ this.strictArrayOrder = b;
+ return this;
+ }
+
+ boolean areSimilar(String expected, String actual) {
+ Object expectedJson = parse(expected);
+ Object actualJson = parse(actual);
+ return compare(expectedJson, actualJson);
+ }
+
+ private Object parse(String s) {
+ try {
+ JSONParser parser = new JSONParser();
+ return parser.parse(s);
+ } catch (Exception e) {
+ throw new IllegalStateException("Invalid JSON: " + s, e);
+ }
+ }
+
+ private boolean compare(@Nullable Object expectedObject, @Nullable Object actualObject) {
+ if (expectedObject == null) {
+ return actualObject == null;
+ }
+ if (actualObject == null) {
+ return false;
+ }
+ if (expectedObject.getClass() != actualObject.getClass()) {
+ return false;
+ }
+ if (expectedObject instanceof JSONArray) {
+ return compareArrays((JSONArray) expectedObject, (JSONArray) actualObject);
+ }
+ if (expectedObject instanceof JSONObject) {
+ return compareMaps((JSONObject) expectedObject, (JSONObject) actualObject);
+ }
+ if (expectedObject instanceof String) {
+ return compareStrings((String) expectedObject, (String) actualObject);
+ }
+ if (expectedObject instanceof Number) {
+ return compareNumbers((Number) expectedObject, (Number) actualObject);
+ }
+ return compareBooleans((Boolean) expectedObject, (Boolean) actualObject);
+ }
+
+ private boolean compareBooleans(Boolean expected, Boolean actual) {
+ return expected.equals(actual);
+ }
+
+ private boolean compareNumbers(Number expected, Number actual) {
+ double d1 = expected.doubleValue();
+ double d2 = actual.doubleValue();
+ if (Double.compare(d1, d2) == 0) {
+ return true;
+ }
+ return (Math.abs(d1 - d2) <= 0.0000001);
+ }
+
+ private boolean compareStrings(String expected, String actual) {
+ if (!strictTimezone) {
+ // two instants with different timezones are considered as identical (2015-01-01T13:00:00+0100 and 2015-01-01T12:00:00+0000)
+ Date expectedDate = tryParseDate(expected);
+ Date actualDate = tryParseDate(actual);
+ if (expectedDate != null && actualDate != null) {
+ return expectedDate.getTime() == actualDate.getTime();
+ }
+ }
+ return expected.equals(actual);
+ }
+
+ private boolean compareArrays(JSONArray expected, JSONArray actual) {
+ if (strictArrayOrder) {
+ return compareArraysByStrictOrder(expected, actual);
+ }
+ return compareArraysByLenientOrder(expected, actual);
+ }
+
+ private boolean compareArraysByStrictOrder(JSONArray expected, JSONArray actual) {
+ if (expected.size() != actual.size()) {
+ return false;
+ }
+
+ for (int index = 0; index < expected.size(); index++) {
+ Object expectedElt = expected.get(index);
+ Object actualElt = actual.get(index);
+ if (!compare(expectedElt, actualElt)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean compareArraysByLenientOrder(JSONArray expected, JSONArray actual) {
+ if (expected.size() > actual.size()) {
+ return false;
+ }
+
+ List remainingActual = new ArrayList(actual);
+ for (Object expectedElement : expected) {
+ // element can be null
+ boolean found = false;
+ for (Object actualElement : remainingActual) {
+ if (compare(expectedElement, actualElement)) {
+ found = true;
+ remainingActual.remove(actualElement);
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ if (!remainingActual.isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean compareMaps(JSONObject expectedMap, JSONObject actualMap) {
+ // each key-value of expected map must exist in actual map
+ for (Object expectedKey : expectedMap.keySet()) {
+ if (!actualMap.containsKey(expectedKey)) {
+ return false;
+ }
+ if (!compare(expectedMap.get(expectedKey), actualMap.get(expectedKey))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @CheckForNull
+ Date tryParseDate(String s) {
+ try {
+ return datetimeFormat.parse(s);
+ } catch (ParseException ignored) {
+ // not a datetime
+ return null;
+ }
+ }
+}