diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-03-19 14:46:58 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-03-20 11:07:24 +0100 |
commit | 88219ea163c159be7d16203be32d8f0b3d7e7d0a (patch) | |
tree | ef96d6c0860ebac6b53ff6f39d521a733c14c25b /sonar-testing-harness/src/main/java/org/sonar/test | |
parent | 1af9a73913f04992e8a9033b29980dd74c6f0a22 (diff) | |
download | sonarqube-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.java | 100 | ||||
-rw-r--r-- | sonar-testing-harness/src/main/java/org/sonar/test/JsonComparison.java | 198 |
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; + } + } +} |