Skip to content

Commit

Permalink
Merge branch 'issue90-parameter-names'
Browse files Browse the repository at this point in the history
* issue90-parameter-names:
  move reusable subscript calculation to abstract base class (#90)
  better method naming and check display name containing parameter names in integration test (#90)
  switch to JUL logger (#90)
  create abstract base class for argument placeholder which formats arguments properly (#90)
  create placeholder to resolve arguments including names (#90)
  • Loading branch information
aaschmid committed Nov 5, 2017
2 parents b2837d8 + bbade55 commit 8ce993f
Show file tree
Hide file tree
Showing 8 changed files with 1,079 additions and 159 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ project(':junit-jupiter-params') {

// configure after properties are set and integration tests are added
subprojects {
tasks.withType(JavaCompile) {
options.compilerArgs += [ "-parameters" ]
}

jar {
manifest {
def now = new Date()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package com.tngtech.junit.dataprovider.placeholder;

import java.util.Arrays;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* This abstract placeholder is able to format arguments of a dataprovider test as comma-separated {@link String}
* according to the given index or range subscript. Furthermore the following arguments are treated specially:
* <table summary="Special {@link String} treatment">
* <tr>
* <th>Argument value</th>
* <th>target {@link String}</th>
* </tr>
* <tr>
* <td>null</td>
* <td>&lt;null&gt;</td>
* </tr>
* <tr>
* <td>&quot;&quot; (= empty string)</td>
* <td>&lt;empty string&gt;</td>
* </tr>
* <tr>
* <td>array (e.g. String[])</td>
* <td>{@code "[" + formatPattern(array) + "]"}</td>
* </tr>
* <tr>
* <td>other</td>
* <td>{@link Object#toString()}</td>
* </tr>
* </table>
*/
abstract class AbstractArgumentPlaceholder extends BasePlaceholder {

protected static final class FromAndTo {
protected final int from;
protected final int to;
protected FromAndTo(int from, int to) {
this.from = from;
this.to = to;
}
}

/**
* {@link String} representation of {@code null}
*/
protected static final String STRING_NULL = "<null>";

/**
* {@link String} representation of {@code ""}
*/
protected static final String STRING_EMPTY = "<empty string>";

/**
* {@link String} representation of an non-printable character
*/
protected static final String STRING_NON_PRINTABLE = "<np>";

AbstractArgumentPlaceholder(String placeholderRegex) {
super(placeholderRegex);
}

/**
* @param placeholder containing the subscript
* @param subscriptStartIndex starting index of first subscript inner digit (to parse part within {@code []})
* @param argumentCount used to apply the parsed subscript values and derive real {@code from} and {@code to}
* @return the wrapped {@link FromAndTo} for the subscript contained in given {@code placeholder} starting at given
* position {@code subscriptStartIndex} and applied on the given {@code argumentCount}
*/
FromAndTo calcFromAndToForSubscriptAndArguments(String placeholder, int subscriptStartIndex,
int argumentCount) {
String subscript = placeholder.substring(subscriptStartIndex, placeholder.length() - 1);

int from = Integer.MAX_VALUE;
int to = Integer.MIN_VALUE;
if (subscript.contains("..")) {
String[] split = subscript.split("\\.\\.");

from = Integer.parseInt(split[0]);
to = Integer.parseInt(split[1]);
} else {
from = Integer.parseInt(subscript);
to = from;
}

from = (from >= 0) ? from : argumentCount + from;
to = (to >= 0) ? to + 1 : argumentCount + to + 1;
return new FromAndTo(from, to);
}

@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "false positive if 'param.toString()' returns 'null'")
protected String format(Object param) {
if (param == null) {
return STRING_NULL;

} else if (param.getClass().isArray()) {
if (param.getClass().getComponentType().isPrimitive()) {
return formatPrimitiveArray(param);
}
return "[" + formatArray((Object[]) param) + "]";

} else if (param instanceof String && ((String) param).isEmpty()) {
return STRING_EMPTY;

}

String result;
if (param instanceof String) {
result = (String) param;
} else {
result = param.toString();
}
if (result == null) { // maybe null if "param.toString()" returns null
return STRING_NULL;
}
result = result.replaceAll("\0", "\\\\0").replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n");
return replaceNonPrintableChars(result, STRING_NON_PRINTABLE);
}

private String formatPrimitiveArray(Object primitiveArray) {
Class<?> componentType = primitiveArray.getClass().getComponentType();

if (boolean.class.equals(componentType)) {
return Arrays.toString((boolean[]) primitiveArray);

} else if (byte.class.equals(componentType)) {
return Arrays.toString((byte[]) primitiveArray);

} else if (char.class.equals(componentType)) {
return Arrays.toString((char[]) primitiveArray);

} else if (short.class.equals(componentType)) {
return Arrays.toString((short[]) primitiveArray);

} else if (int.class.equals(componentType)) {
return Arrays.toString((int[]) primitiveArray);

} else if (long.class.equals(componentType)) {
return Arrays.toString((long[]) primitiveArray);

} else if (float.class.equals(componentType)) {
return Arrays.toString((float[]) primitiveArray);

} else if (double.class.equals(componentType)) {
return Arrays.toString((double[]) primitiveArray);
}
throw new IllegalStateException("Called 'formatPrimitiveArray' on non-primitive array");
}

private String formatArray(Object[] array) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < array.length; i++) {
stringBuilder.append(format(array[i]));
if (i < array.length - 1) {
stringBuilder.append(", ");
}
}
return stringBuilder.toString();
}

private String replaceNonPrintableChars(String input, String replacement) {
StringBuilder result = new StringBuilder();
for (int offset = 0; offset < input.length();) {
int codePoint = input.codePointAt(offset);
offset += Character.charCount(codePoint);

// Replace invisible control characters and unused code points
switch (Character.getType(codePoint)) {
case Character.CONTROL: // \p{Cc}
case Character.FORMAT: // \p{Cf}
case Character.PRIVATE_USE: // \p{Co}
case Character.SURROGATE: // \p{Cs}
case Character.UNASSIGNED: // \p{Cn}
result.append(replacement);
break;

default:
result.append(Character.toChars(codePoint));
break;
}
}
return result.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,86 +1,31 @@
package com.tngtech.junit.dataprovider.placeholder;

import java.util.Arrays;
import java.util.List;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* This placeholder format the arguments of a dataprovider test as comma-separated {@link String} according to the given
* index or range subscript. Furthermore the following arguments are treated specially:
* <table summary="Special {@link String} treatment">
* <tr>
* <th>Argument value</th>
* <th>target {@link String}</th>
* </tr>
* <tr>
* <td>null</td>
* <td>&lt;null&gt;</td>
* </tr>
* <tr>
* <td>&quot;&quot; (= empty string)</td>
* <td>&lt;empty string&gt;</td>
* </tr>
* <tr>
* <td>array (e.g. String[])</td>
* <td>{@code "[" + formatPattern(array) + "]"}</td>
* </tr>
* <tr>
* <td>other</td>
* <td>{@link Object#toString()}</td>
* </tr>
* </table>
* index or range subscript. For a list of special argument treatments, see {@link AbstractArgumentPlaceholder}.
*
* @see AbstractArgumentPlaceholder
*/
public class ArgumentPlaceholder extends BasePlaceholder {

/**
* {@link String} representation of {@code null}
*/
protected static final String STRING_NULL = "<null>";

/**
* {@link String} representation of {@code ""}
*/
protected static final String STRING_EMPTY = "<empty string>";

/**
* {@link String} representation of an non-printable character
*/
protected static final String STRING_NON_PRINTABLE = "<np>";

public class ArgumentPlaceholder extends AbstractArgumentPlaceholder {

public ArgumentPlaceholder() {
super("%[ap]\\[(-?[0-9]+|-?[0-9]+\\.\\.-?[0-9]+)\\]");
}

@Override
protected String getReplacementFor(String placeholder, ReplacementData data) {
String subscript = placeholder.substring(3, placeholder.length() - 1);

int from = Integer.MAX_VALUE;
int to = Integer.MIN_VALUE;
if (subscript.contains("..")) {
String[] split = subscript.split("\\.\\.");

from = Integer.parseInt(split[0]);
to = Integer.parseInt(split[1]);
} else {
from = Integer.parseInt(subscript);
to = from;
}

List<Object> arguments = data.getArguments();
from = (from >= 0) ? from : arguments.size() + from;
to = (to >= 0) ? to + 1 : arguments.size() + to + 1;
return formatAll(arguments.subList(from, to));
FromAndTo fromAndTo = calcFromAndToForSubscriptAndArguments(placeholder, 3, data.getArguments().size());
return formatAll(data.getArguments().subList(fromAndTo.from, fromAndTo.to));
}

/**
* Formats the given arguments by retrieving it's {@link String} representation and separate it by comma (=
* {@code ,}).
*
* @param arguments to be formatted
* @return the {@link String} representation of the given {@link Object}{@code []}
* @return the {@link String} representation of the given {@link List}{@code <Object>}
*/
protected String formatAll(List<Object> arguments) {
StringBuilder stringBuilder = new StringBuilder();
Expand All @@ -92,88 +37,4 @@ protected String formatAll(List<Object> arguments) {
}
return stringBuilder.toString();
}


@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "false positive if 'param.toString()' returns 'null'")
protected String format(Object param) {
if (param == null) {
return STRING_NULL;

} else if (param.getClass().isArray()) {
if (param.getClass().getComponentType().isPrimitive()) {
return formatPrimitiveArray(param);
}
return "[" + formatAll(Arrays.asList((Object[]) param)) + "]";

} else if (param instanceof String && ((String) param).isEmpty()) {
return STRING_EMPTY;

}

String result;
if (param instanceof String) {
result = (String) param;
} else {
result = param.toString();
}
if (result == null) { // maybe null if "param.toString()" returns null
return STRING_NULL;
}
result = result.replaceAll("\0", "\\\\0").replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n");
return replaceNonPrintableChars(result, STRING_NON_PRINTABLE);
}

private String formatPrimitiveArray(Object primitiveArray) {
Class<?> componentType = primitiveArray.getClass().getComponentType();

if (boolean.class.equals(componentType)) {
return Arrays.toString((boolean[]) primitiveArray);

} else if (byte.class.equals(componentType)) {
return Arrays.toString((byte[]) primitiveArray);

} else if (char.class.equals(componentType)) {
return Arrays.toString((char[]) primitiveArray);

} else if (short.class.equals(componentType)) {
return Arrays.toString((short[]) primitiveArray);

} else if (int.class.equals(componentType)) {
return Arrays.toString((int[]) primitiveArray);

} else if (long.class.equals(componentType)) {
return Arrays.toString((long[]) primitiveArray);

} else if (float.class.equals(componentType)) {
return Arrays.toString((float[]) primitiveArray);

} else if (double.class.equals(componentType)) {
return Arrays.toString((double[]) primitiveArray);
}
return "";
}

private String replaceNonPrintableChars(String input, String replacement) {
StringBuilder result = new StringBuilder();
for (int offset = 0; offset < input.length(); ) {
int codePoint = input.codePointAt(offset);
offset += Character.charCount(codePoint);

// Replace invisible control characters and unused code points
switch (Character.getType(codePoint)) {
case Character.CONTROL: // \p{Cc}
case Character.FORMAT: // \p{Cf}
case Character.PRIVATE_USE: // \p{Co}
case Character.SURROGATE: // \p{Cs}
case Character.UNASSIGNED: // \p{Cn}
result.append(replacement);
break;

default:
result.append(Character.toChars(codePoint));
break;
}
}
return result.toString();
}
}
Loading

0 comments on commit 8ce993f

Please sign in to comment.