Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current (7.13.0)
Fixed: GITHUB-3378, GITHUB-3364: ReflectionRecipes no longer treats Character as assignable to short (char does not widen to short). MethodMatcherException stringifies primitive arrays instead of throwing ClassCastException, leftover data-provider arguments still produce a diagnostic when the injection target is a constructor holder, and the unused out-of-bounds lenientMatch helpers are removed (Burak Kalaycı)
Fixed: GITHUB-3366: ToStringHelper.omitNulls() and omitEmptyStrings() now inspect the original value instead of the already-stringified form, so ITestResult.toString() no longer renders output={null} when a result has no reporter output (Burak Kalaycı)
New: GITHUB-3322: A suite file may declare the schema instead of a doctype, with xsi:noNamespaceSchemaLocation="https://testng.org/testng-1.1.xsd" on <suite>, and is then validated against testng-1.1.xsd. A suite declaring neither is validated against the schema too, where it was previously validated by nothing: error() discarded every violation unless a doctype had been seen, and the parser was configured for DTD validation, so a document carrying no DTD had no grammar to violate. A suite declaring a doctype keeps being validated against the DTD, so nothing changes for the files that have one. TestNG always uses the schema it ships, never the URL the file names, so a parse still touches no network (Julien Herr)
Changed: GITHUB-3322: The hint printed for a suite file that declares no grammar now offers the schema first and the doctype second, and is no longer printed for a file that declares a schema (Julien Herr)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.testng.internal.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
Expand Down Expand Up @@ -43,7 +44,7 @@ static String generateMessage(
}

public static String generateMessage(
final String message, final Method method, final Object[] args) {
final String message, final @Nullable Method method, final Object[] args) {
return generateMessage(
message,
method != null ? method.getName() : null,
Expand Down Expand Up @@ -87,10 +88,14 @@ private static String generateMessage(
}

private static String stringify(Object object) {
if (object.getClass().isArray()) {
return Arrays.toString((Object[]) object);
} else {
if (!object.getClass().isArray()) {
return object.toString();
}
final int length = Array.getLength(object);
final Object[] elements = new Object[length];
for (int i = 0; i < length; i++) {
elements[i] = Array.get(object, i);
}
return Arrays.toString(elements);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestNGException;
Expand Down Expand Up @@ -67,7 +68,7 @@ private static void initAssignableMapping() {
ASSIGNABLE_MAPPING.put(
long.class, Arrays.asList(Integer.class, Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(int.class, Arrays.asList(Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(short.class, Arrays.asList(Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(short.class, Arrays.asList(Byte.class));
}

private ReflectionRecipes() {
Expand Down Expand Up @@ -170,7 +171,7 @@ public static Class<?>[] classesFromParameters(final Parameter[] parameters) {
* @param method any valid method.
* @return extracted method parameters.
*/
public static Parameter[] getMethodParameters(final Method method) {
public static Parameter[] getMethodParameters(final @Nullable Method method) {
if (method == null) {
return new Parameter[] {};
}
Expand Down Expand Up @@ -282,37 +283,6 @@ public static boolean exactMatch(final Class<?>[] classes, final Object[] args)
return matching;
}

/**
* Matches an array of parameters to an array of instances.
*
* @return matches or not
* @see #lenientMatch(Class[], Object[])
*/
public static boolean lenientMatch(final Parameter[] parameters, final Object[] args) {
return lenientMatch(classesFromParameters(parameters), args);
}

/**
* Matches an array of class instances to an array of instances. Such that {int, boolean, float}
* matches {int, boolean}
*
* @param classes array of class instances to check against.
* @param args instances to be verified.
* @return matches or not
*/
public static boolean lenientMatch(final Class<?>[] classes, final Object[] args) {
boolean matching = true;
int i = 0;
for (final Class<?> clazz : classes) {
matching = ReflectionRecipes.isInstanceOf(clazz, args[i]);
i++;
if (!matching) {
break;
}
}
return matching;
}

/**
* Omits 1. org.testng.ITestContext or its implementations from input array 2.
* org.testng.ITestResult or its implementations from input array 3. org.testng.xml.XmlTest or its
Expand Down Expand Up @@ -431,17 +401,19 @@ private static Object[] nativelyInject(
String prefix =
"Missing one or more parameters that are being injected by the data provider. "
+ "Please add the below arguments to the ";
String msg = null;
if (injectionMethod instanceof Method) {
msg =
MethodMatcherException.generateMessage(
prefix + "method.", (Method) injectionMethod, queue.backingList.toArray());
} else if (injectionMethod instanceof Constructor) {
final String msg;
if (injectionMethod instanceof Constructor) {
msg =
MethodMatcherException.generateMessage(
prefix + "constructor.",
(Constructor<?>) injectionMethod,
queue.backingList.toArray());
} else {
msg =
MethodMatcherException.generateMessage(
prefix + "method.",
injectionMethod instanceof Method ? (Method) injectionMethod : null,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is not a method, it should fail

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, unknown holder now throws instead of falling back to Method: null. null Method still keeps the leftover diagnostic.

queue.backingList.toArray());
}

boolean block = RuntimeBehavior.useStrictParameterMatching();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.testng.internal.reflect.ReflectionRecipes.exactMatch;
import static org.testng.internal.reflect.ReflectionRecipes.getMethodParameters;
import static org.testng.internal.reflect.ReflectionRecipes.isOrImplementsInterface;
Expand All @@ -24,6 +25,7 @@
import org.testng.annotations.NoInjection;
import org.testng.annotations.Test;
import org.testng.internal.reflect.InjectableParameter;
import org.testng.internal.reflect.MethodMatcherException;
import org.testng.internal.reflect.ReflectionRecipes;
import org.testng.log4testng.Logger;
import org.testng.xml.XmlTest;
Expand Down Expand Up @@ -235,6 +237,9 @@ public Object[][] primitiveAndArgument() {
// Would narrow, so it does not match.
{int.class, 1L, false},
{float.class, 1.0d, false},
// byte widens to short; char does not (JLS 5.1.2).
{short.class, (byte) 1, true},
{short.class, 'a', false},
};
}

Expand All @@ -244,6 +249,43 @@ public void testIsInstanceOfHonoursWidening(
assertThat(ReflectionRecipes.isInstanceOf(primitive, argument)).isEqualTo(expected);
}

@Test
public void generateMessageStringifiesPrimitiveArray() throws Exception {
final Method method = ReflectionRecipesTest.class.getMethod("oneInt", int.class);
final String message =
MethodMatcherException.generateMessage("probe", method, new Object[] {new int[] {1, 2}});
assertThat(message).contains("[1, 2]");
}

@Test
public void leftoverArgumentsWithNullMethodKeepDiagnostic() {
final String previous = System.getProperty("strictParameterMatch");
System.setProperty("strictParameterMatch", "true");
try {
assertThatThrownBy(
() ->
ReflectionRecipes.inject(
getMethodParameters(ReflectionRecipesTest.class, "oneInt"),
InjectableParameter.Assistant.ALL_INJECTS,
new Object[] {1, "leftover"},
(Method) null,
null,
null))
.isInstanceOf(MethodMatcherException.class)
.hasMessageContaining("Missing one or more parameters")
.hasMessageContaining("leftover")
.hasMessageContaining("Method: null");
} finally {
if (previous == null) {
System.clearProperty("strictParameterMatch");
} else {
System.setProperty("strictParameterMatch", previous);
}
}
}

public static void oneInt(int value) {}

private interface T {
void s0(TestContextJustForTesting testContext, int i, Boolean b);

Expand Down
Loading