Browse Source

Added binary compatibility checks to build.

pull/61/head
Kalle Stenflo 10 years ago
parent
commit
bbcec25cf7
  1. 4
      build.gradle
  2. 1
      changelog.md
  3. 138
      gradle/binaryCompatibility.gradle
  4. 153
      gradle/binarycompat-report.groovy
  5. 3
      json-path/build.gradle
  6. 29
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java
  7. 91
      json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java
  8. 69
      json-path/src/test/java/com/jayway/jsonpath/JsonSmartMappingProviderTest.java

4
build.gradle

@ -5,6 +5,7 @@ buildscript {
dependencies {
classpath 'me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1'
classpath 'com.github.jengelman.gradle.plugins:shadow:0.8'
classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.1.0'
}
}
@ -73,4 +74,5 @@ task wrapper(type: Wrapper) {
}
//Task used by Heroku for staging
task stage(dependsOn: [':json-path-web-test:clean', 'json-path-web-test:shadowJar']) {}
task stage(dependsOn: [':json-path-web-test:clean', 'json-path-web-test:shadowJar']) {}
apply from: "$rootDir/gradle/binaryCompatibility.gradle"

1
changelog.md

@ -19,6 +19,7 @@ In The Pipe
* Negate exist checks in inline filters (not defined or null)
`parse(JSON_DOCUMENT).read("$.store.book[?(!@.isbn)]")`
* Improved object mapping with Jackson and Gson (now handles generic types)
* JacksonTreeJsonProvider supporting path operations on `com.fasterxml.jackson.databind.JsonNode`
* Exceptions thrown by JsonPath.compile are now wrapped in an InvalidPathException
* Fixed Deep scanning issue (#60)

138
gradle/binaryCompatibility.gradle

@ -0,0 +1,138 @@
/*
* Copyright 2003-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
buildscript {
// this block should not be necessary, but for some reason it fails without!
repositories {
jcenter()
}
dependencies {
classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.1.0'
}
}
task checkBinaryCompatibility {
description = 'Generates binary compatibility reports'
}
if (JavaVersion.current().isJava7Compatible()) {
apply plugin: 'me.champeau.gradle.japicmp'
def referenceMinorVersion = '1.1.0'
def reportGenerator = { model ->
outputProcessor {
def skipClass = { c -> c.fullyQualifiedName.startsWith('com.jayway.jsonpath.internal') || c.fullyQualifiedName.contains('$')}
def skipMethod = { c, m -> skipClass(c) || m.name =~ /access\$[0-9]+/ }
def violations = [:].withDefault {
// key = class name
// value = map of violations
[:].withDefault { [] }
}
removedMethod { c, m ->
if (!skipMethod(c, m)) {
def level = m.name.startsWith('super$') ? 'warning' : 'error'
violations[c.fullyQualifiedName][level] << "Method ${m.name} has been removed"
}
}
removedClass { c ->
if (!skipClass(c)) {
violations[c.fullyQualifiedName].error << "Class has been removed"
}
}
modifiedMethod { c, m ->
if (!skipMethod(c, m)) {
violations[c.fullyQualifiedName].warning << """<p>Method ${m.name} has been modified</p>
<p>From <pre>${m.oldMethod.get()?.longName}</pre> to <pre>${m.newMethod.get()?.longName}</pre></p>"""
}
}
newClass { c ->
if (!skipClass(c)) {
violations[c.fullyQualifiedName].info << "Class has been added"
}
}
newMethod { c, m ->
if (!skipMethod(c, m)) {
violations[c.fullyQualifiedName].info << """<p>Method ${m.name} has been added</p>
<p>Signature: <pre>${m.newMethod.get()?.longName}</pre></p>"""
}
}
after {
model.violations = violations
}
}
}
// using a global engine for all tasks in order to increase performance
def configDir = file("$rootDir/gradle")
def templateFile = 'binarycompat-report.groovy'
def templateConfiguration = new TemplateConfiguration()
templateConfiguration.with {
autoIndent = true
autoNewLine = true
}
def engine = new MarkupTemplateEngine(this.class.classLoader, configDir, templateConfiguration)
subprojects {
task japicmp(type: me.champeau.gradle.ArtifactJapicmpTask) {
dependsOn jar
//baseline = "com.jayway.jsonpath:${project.name}:${referenceMinorVersion}@jar"
baseline = "com.jayway.jsonpath:${project.name}:+@jar"
to = jar.archivePath
accessModifier = 'protected'
onlyModified = true
failOnModification = false
txtOutputFile = file("$buildDir/reports/japi.txt")
def htmlReportFile = file("${buildDir}/reports/binary-compat-${project.name}.html")
inputs.file file("$configDir/$templateFile")
inputs.file templateFile
outputs.file htmlReportFile
def model = [title : "Binary compatibility report for ${project.name}",
project : project,
baseline: baseline,
archive : to.name]
outputProcessor(reportGenerator.curry(model))
doLast {
htmlReportFile.withWriter('utf-8') { wrt ->
engine.createTemplateByPath(templateFile).make(model).writeTo(wrt)
}
}
}
}
subprojects {
check.dependsOn(checkBinaryCompatibility)
tasks.withType(me.champeau.gradle.ArtifactJapicmpTask) { task ->
checkBinaryCompatibility.dependsOn(task)
}
}
}

153
gradle/binarycompat-report.groovy

@ -0,0 +1,153 @@
/*
* Copyright 2003-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A template which generates an HTML report from the bincompat XML report
*/
modelTypes = {
String title
String baseline
String archive
Map<String,Map<String,List<String>>> violations
}
def severityMapping = [
error : 'danger',
warning: 'warning',
info : 'info',
ignore : 'success'
]
yieldUnescaped '<!DOCTYPE html>'
html {
head {
meta 'charset': "utf-8"
meta 'http-equiv': "content-type", content: "text/html; charset=utf-8"
meta 'http-equiv': "X-UA-Compatible", content: "IE=edge"
meta name: "viewport", content: "width=device-width, initial-scale=1"
title(title)
link href: "http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css", rel: "stylesheet"
link href: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css", rel: "stylesheet"
link href: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css", rel: "stylesheet"
}
body {
div(class:'navbar navbar-inverse navbar-fixed-top', role:'navigation') {
div(class:'container') {
div(class:'navbar-header') {
button(type:'button', class:'navbar-toggle', 'data-toggle':'collapse', 'data-target':'navbar-collaspe') {
span(class:'sr-only', 'Toggle navigation')
span(class:'icon-bar'){}
span(class:'icon-bar'){}
span(class:'icon-bar'){}
}
a(class:'navbar-brand',href:'#', 'Binary compatibility report')
}
div(class:'navbar-collapse collapse') {
ul(class:"nav navbar-nav") {
li(class: 'dropdown') {
a(id: 'severityDropdown', href: '#', class: 'dropdown-toggle', 'data-toggle': 'dropdown', 'Severity <span class="caret"></span>')
ul(class: "dropdown-menu dropdown-severity", role: "menu") {
li(role: 'presentation', class: 'active') {
a(role: 'menuitem', tabindex: '-1', href: '#', 'All levels')
}
li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Error') }
li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Warning') }
li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Info') }
}
}
}
}
}
}
div(class: 'container') {
div(class:'jumbotron') {
div(class:'container') {
div(class: 'page-header') {
h1 'Binary compatibility'
p "Comparing ${archive} to reference ${baseline}"
p {
yield "Be warned that this report is not perfect and depends on what "
a(href: 'https://github.com/siom79/japicmp', 'JApicmp')
yield " is capable to detect."
}
}
}
}
violations.each { fqcn, classViolations ->
def errors = classViolations.keySet()
def severities = errors.collect { "severity-${it}" }
div(class: "panel panel-default ${severities.join(' ')}") {
div(class: "panel-heading") {
h3(class: 'panel-title', "Class $fqcn")
}
div(class: 'panel-body') {
table(class: "table table-striped table-bordered") {
tbody {
classViolations.each { err, list ->
list.each { item ->
tr(class: "bincompat-error severity-${err}") {
td {
h4 {
span(class: "label label-${severityMapping[err]}", err.capitalize())
}
}
td { span(item) }
}
}
}
}
}
}
}
}
script(src: "http://code.jquery.com/jquery-1.11.0.min.js") {}
script(src: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js") {}
script {
yieldUnescaped '''
$(document).ready(function () {
var severity = null;
doFilter();
function doFilter() {
var severityClass = "severity-" + severity;
$('.panel').hide();
$('.bincompat-error').hide();
$('.bincompat-error').filter(function () {
return (severity==null || $(this).hasClass(severityClass));
}).show();
$('.panel').filter(function () {
return (severity==null || $(this).hasClass(severityClass));
}).show();
}
$(".dropdown-severity li a").click(function() {
severity = $(this).text().toLowerCase();
if (severity==="all levels") {
severity = null;
}
doFilter();
});
});'''
}
}
}
}

3
json-path/build.gradle

@ -1,5 +1,6 @@
apply from: "$rootDir/gradle/publishMaven.gradle"
displayName = "Json Path"
description = "Java port of Stefan Goessner JsonPath."
@ -13,9 +14,9 @@ jar {
dependencies {
compile libs.jsonSmart
compile libs.slf4jApi
compile libs.jacksonDatabind, optional
compile libs.gson, optional
compile libs.slf4jApi
testCompile libs.test
}

29
json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java

@ -42,9 +42,13 @@ public class JsonSmartMappingProvider implements MappingProvider {
static {
DEFAULT.registerReader(Long.class, new LongReader());
DEFAULT.registerReader(long.class, new LongReader());
DEFAULT.registerReader(Integer.class, new IntegerReader());
DEFAULT.registerReader(int.class, new IntegerReader());
DEFAULT.registerReader(Double.class, new DoubleReader());
DEFAULT.registerReader(double.class, new DoubleReader());
DEFAULT.registerReader(Float.class, new FloatReader());
DEFAULT.registerReader(float.class, new FloatReader());
DEFAULT.registerReader(BigDecimal.class, new BigDecimalReader());
DEFAULT.registerReader(String.class, new StringReader());
DEFAULT.registerReader(Date.class, new DateReader());
@ -94,7 +98,7 @@ public class JsonSmartMappingProvider implements MappingProvider {
@Override
public <T> T map(Object source, TypeRef<T> targetType, Configuration configuration) {
throw new UnsupportedOperationException("Not supported! Use Jackson or Gson provider");
throw new UnsupportedOperationException("Json-smart provider does not support TypeRef! Use a Jackson or Gson based provider");
}
private static class StringReader extends JsonReaderI<String> {
@ -116,7 +120,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
if (Long.class.isAssignableFrom(src.getClass())) {
if(Integer.class.isAssignableFrom(src.getClass())){
return (Integer) src;
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).intValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).intValue();
@ -138,7 +144,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
if (Integer.class.isAssignableFrom(src.getClass())) {
if(Long.class.isAssignableFrom(src.getClass())){
return (Long) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).longValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).longValue();
@ -160,7 +168,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
if (Integer.class.isAssignableFrom(src.getClass())) {
if(Double.class.isAssignableFrom(src.getClass())){
return (Double) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).doubleValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).doubleValue();
@ -182,7 +192,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
if (Integer.class.isAssignableFrom(src.getClass())) {
if(Float.class.isAssignableFrom(src.getClass())){
return (Float) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).floatValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).floatValue();
@ -215,10 +227,11 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
if(Long.class.isAssignableFrom(src.getClass())){
if(Date.class.isAssignableFrom(src.getClass())){
return (Date) src;
} else if(Long.class.isAssignableFrom(src.getClass())){
return new Date((Long) src);
}
else if(String.class.isAssignableFrom(src.getClass())){
} else if(String.class.isAssignableFrom(src.getClass())){
try {
return DateFormat.getInstance().parse(src.toString());
} catch (ParseException e) {

91
json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java

@ -0,0 +1,91 @@
package com.jayway.jsonpath;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat;
public class JsonProviderTest extends BaseTest {
private static final String JSON =
"[" +
"{\n" +
" \"foo\" : \"foo0\",\n" +
" \"bar\" : 0,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp0\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo1\",\n" +
" \"bar\" : 1,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp1\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo2\",\n" +
" \"bar\" : 2,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp2\"}" +
"}" +
"]";
@Test
public void strings_are_unwrapped() {
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
}
@Test
public void integers_are_unwrapped() {
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void ints_are_unwrapped() {
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void list_of_numbers() {
TypeRef<List<Double>> typeRef = new TypeRef<List<Double>>() {};
assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
}
@Test
public void test_type_ref() throws IOException {
TypeRef<List<FooBarBaz<Sub>>> typeRef = new TypeRef<List<FooBarBaz<Sub>>>() {};
assertThat(using(JACKSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
assertThat(using(GSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
}
public static class FooBarBaz<T> {
public T gen;
public String foo;
public Long bar;
public boolean baz;
}
public static class Sub {
public String prop;
}
}

69
json-path/src/test/java/com/jayway/jsonpath/JsonSmartMappingProviderTest.java

@ -1,69 +0,0 @@
package com.jayway.jsonpath;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.parse;
import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat;
public class JsonSmartMappingProviderTest {
private static final String JSON =
"[" +
"{\n" +
" \"foo\" : \"foo0\",\n" +
" \"bar\" : 0,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"eric\" : \"yepp\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo1\",\n" +
" \"bar\" : 1,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"eric\" : \"yepp\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo2\",\n" +
" \"bar\" : 2,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"eric\" : \"yepp\"}" +
"}" +
"]";
@Test
public void class_mapping() {
Object map = Configuration.defaultConfiguration().jsonProvider().createMap();
Configuration.defaultConfiguration().jsonProvider().setProperty(map, "eric", "eric-val");
Gen gen = parse(map).read("$", Gen.class);
assertThat(gen.eric).isEqualTo("eric-val");
}
@Test(expected = UnsupportedOperationException.class)
public void test_type_ref() throws IOException {
TypeRef<List<FooBarBaz>> typeRef = new TypeRef<List<FooBarBaz>>() {};
List gen = parse(JSON).read("$", typeRef);
}
public static class FooBarBaz {
public Gen gen;
public String foo;
public Long bar;
public boolean baz;
}
public static class Gen {
public String eric;
}
}
Loading…
Cancel
Save