Skip to content

Commit

Permalink
Merge pull request gradle#10858 from gradle/lptr/validation/show-fail…
Browse files Browse the repository at this point in the history
…ed-task-type-in-exception

Display nice type names consistently for validation warnings
  • Loading branch information
lptr authored Sep 25, 2019
2 parents ac565d2 + b349f9b commit 084f011
Show file tree
Hide file tree
Showing 20 changed files with 109 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ task work {

expect:
fails("work")
failure.assertHasDescription("A problem was found with the configuration of task ':work'.")
failure.assertHasDescription("A problem was found with the configuration of task ':work' (type 'DefaultTask').")
failure.assertHasCause("File '$link' specified for property '\$1' does not exist.")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ class NestedInputIntegrationTest extends AbstractIntegrationSpec {

expect:
fails "myTask"
failure.assertHasDescription("A problem was found with the configuration of task ':myTask'.")
failure.assertHasDescription("A problem was found with the configuration of task ':myTask' (type 'TaskWithAbsentNestedInput').")
failure.assertHasCause("No value has been specified for property 'nested'.")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class TaskInputFilePropertiesIntegrationTest extends AbstractIntegrationSpec {

expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("Value 'task ':dependencyTask'' specified for property 'input' cannot be converted to a ${targetType}.")

where:
Expand Down Expand Up @@ -108,7 +108,7 @@ class TaskInputFilePropertiesIntegrationTest extends AbstractIntegrationSpec {

expect:
fails "customTask"
failure.assertHasDescription("A problem was found with the configuration of task ':customTask'.")
failure.assertHasDescription("A problem was found with the configuration of task ':customTask' (type 'CustomTask').")
failure.assertHasCause("Value 'task ':dependencyTask'' specified for property 'input' cannot be converted to a ${targetType}.")

where:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ task someTask(type: SomeTask) {
"""
expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("No value has been specified for property 'input'.")
}
Expand All @@ -495,7 +495,7 @@ task someTask(type: SomeTask) {
"""
expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("No value has been specified for property 'input'.")
where:
Expand Down Expand Up @@ -527,7 +527,7 @@ task someTask(type: SomeTask) {
"""
expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("No value has been specified for property 'output'.")
where:
Expand Down Expand Up @@ -560,7 +560,7 @@ task someTask(type: SomeTask) {
expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("$type '${file("missing")}' specified for property 'input' does not exist.")
where:
Expand All @@ -582,7 +582,7 @@ task someTask(type: SomeTask) {
expect:
fails "test"
failure.assertHasDescription("A problem was found with the configuration of task ':test'.")
failure.assertHasDescription("A problem was found with the configuration of task ':test' (type 'DefaultTask').")
failure.assertHasCause("${type.capitalize()} '${file(path)}' specified for property 'input' is not a $type.")
where:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class DefaultTypeMetadataStoreTest extends Specification {
def typeAnnotationHandler = Stub(TypeAnnotationHandler)
_ * typeAnnotationHandler.annotationType >> CustomCacheable
_ * typeAnnotationHandler.validateTypeMetadata(_, _) >> { Class type, TypeValidationContext context ->
context.visitTypeProblem(WARNING, Object, "type is broken")
context.visitTypeProblem(WARNING, type, "type is broken")
}

def metadataStore = new DefaultTypeMetadataStore([typeAnnotationHandler], [], [], typeAnnotationMetadataStore, cacheFactory)
Expand All @@ -181,7 +181,7 @@ class DefaultTypeMetadataStoreTest extends Specification {
def typeMetadata = metadataStore.getTypeMetadata(TypeWithCustomAnnotation)

then:
collectProblems(typeMetadata) == ["Type '$Object.name': type is broken." as String]
collectProblems(typeMetadata) == ["Type 'DefaultTypeMetadataStoreTest.TypeWithCustomAnnotation': type is broken." as String]
}

@Unroll
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.model.internal.type.ModelType
import spock.lang.Specification

import java.nio.file.Path
Expand Down Expand Up @@ -257,6 +258,6 @@ class PropertyValidationAccessTest extends Specification {
}

private static Set<String> validationProblems(Class<?> task, List messages) {
messages.collect { "Type '${task.name}': ${it}." }*.toString() as Set
messages.collect { "Type '${ModelType.of(task).displayName}': ${it}." }*.toString() as Set
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso
fails "badTask", "--continue"
then:
['lib', 'app', 'util'].each {
failure.assertHasDescription("A problem was found with the configuration of task ':${it}:badTask'.")
failure.assertHasDescription("A problem was found with the configuration of task ':${it}:badTask' (type 'DefaultTask').")
failure.assertHasCause("The output ${file("${it}/build/${forbiddenPath}")} must not be in a reserved location.")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency
then:
failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Parameters_Decorated@.* of artifact transform MakeGreen'))
failure.assertHasCause('Some problems were found with the configuration of the artifact transform parameter MakeGreen.Parameters.')
failure.assertHasCause("Type 'MakeGreen\$Parameters': Cannot use @CacheableTask on type. This annotation can only be used with Task types.")
failure.assertHasCause("Type 'MakeGreen\$Parameters': Cannot use @CacheableTransform on type. This annotation can only be used with TransformAction types.")
failure.assertHasCause("Type 'MakeGreen.Parameters': Cannot use @CacheableTask on type. This annotation can only be used with Task types.")
failure.assertHasCause("Type 'MakeGreen.Parameters': Cannot use @CacheableTransform on type. This annotation can only be used with TransformAction types.")
}
@Unroll
Expand Down Expand Up @@ -820,7 +820,7 @@ abstract class MakeGreen implements TransformAction<TransformParameters.None> {
expect:
fails('broken')
failure.assertHasDescription("A problem was found with the configuration of task ':broken'.")
failure.assertHasDescription("A problem was found with the configuration of task ':broken' (type 'MyTask').")
failure.assertHasCause("Type 'MyTask': Cannot use @CacheableTransform on type. This annotation can only be used with TransformAction types.")
}
Expand All @@ -844,7 +844,7 @@ abstract class MakeGreen implements TransformAction<TransformParameters.None> {
expect:
// Probably should be eager
fails('broken')
failure.assertHasDescription("Some problems were found with the configuration of task ':broken'.")
failure.assertHasDescription("Some problems were found with the configuration of task ':broken' (type 'MyTask').")
failure.assertHasCause("Type 'Options': Cannot use @CacheableTask on type. This annotation can only be used with Task types.")
failure.assertHasCause("Type 'Options': Cannot use @CacheableTransform on type. This annotation can only be used with TransformAction types.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ class IncrementalExecutionIntegrationTest extends Specification {

then:
def ex = thrown WorkValidationException
ex.causes*.message as List == ["Type 'java.lang.Object': Validation error."]
ex.causes*.message as List == ["Type '$Object.simpleName': Validation error."]
}

List<String> inputFilesRemoved(Map<String, List<File>> removedFiles) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.gradle.internal.execution.steps;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import org.gradle.api.InvalidUserDataException;
import org.gradle.internal.execution.Context;
Expand All @@ -27,8 +28,8 @@
import org.gradle.internal.reflect.MessageFormattingTypeValidationContext;
import org.gradle.internal.reflect.TypeValidationContext;
import org.gradle.internal.reflect.TypeValidationContext.Severity;
import org.gradle.model.internal.type.ModelType;

import java.util.List;
import java.util.stream.Collectors;

public class ValidateStep<C extends Context, R extends Result> implements Step<C, R> {
Expand All @@ -55,29 +56,45 @@ public R execute(C context) {
warnings.forEach(warningReporter::reportValidationWarning);

if (!errors.isEmpty()) {
String displayName = context.getWork().getDisplayName();
String errorMessage = errors.size() == 1
? String.format("A problem was found with the configuration of %s.", displayName)
: String.format("Some problems were found with the configuration of %s.", displayName);
List<InvalidUserDataException> causes = errors.stream()
.limit(5)
.sorted()
.map(InvalidUserDataException::new)
.collect(Collectors.toList());
throw new WorkValidationException(errorMessage, causes);
throw new WorkValidationException(
String.format("%s found with the configuration of %s (%s).",
errors.size() == 1
? "A problem was"
: "Some problems were",
context.getWork().getDisplayName(),
describeTypesChecked(validationContext.getTypes())
),
errors.stream()
.limit(5)
.sorted()
.map(InvalidUserDataException::new)
.collect(Collectors.toList())
);
}
return delegate.execute(context);
}

private static String describeTypesChecked(ImmutableList<Class<?>> types) {
return types.size() == 1
? "type '" + getTypeDisplayName(types.get(0)) + "'"
: "types '" + types.stream().map(ValidateStep::getTypeDisplayName).collect(Collectors.joining("', '")) + "'";
}

private static String getTypeDisplayName(Class<?> type) {
return ModelType.of(type).getDisplayName();
}

public interface ValidationWarningReporter {
void reportValidationWarning(String warning);
}

private static class DefaultWorkValidationContext implements UnitOfWork.WorkValidationContext {
private final ImmutableMultimap.Builder<Severity, String> problems = ImmutableMultimap.builder();
private final ImmutableList.Builder<Class<?>> types = ImmutableList.builder();

@Override
public TypeValidationContext createContextFor(Class<?> type, boolean cacheable) {
types.add(type);
return new MessageFormattingTypeValidationContext(null) {
@Override
protected void recordProblem(Severity severity, String message) {
Expand All @@ -92,5 +109,9 @@ protected void recordProblem(Severity severity, String message) {
public ImmutableMultimap<Severity, String> getProblems() {
return problems.build();
}

public ImmutableList<Class<?>> getTypes() {
return types.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ class ValidateStepTest extends ContextInsensitiveStepSpec {

then:
def ex = thrown WorkValidationException
ex.message == "A problem was found with the configuration of job ':test'."
ex.message == "A problem was found with the configuration of job ':test' (type 'ValidateStepTest.JobType')."
ex.causes.size() == 1
ex.causes[0].message == "Type '$Object.name': Validation error."
ex.causes[0].message == "Type '$Object.simpleName': Validation error."

_ * work.validate(_ as WorkValidationContext) >> { WorkValidationContext validationContext ->
validationContext.createContextFor(Object, true).visitTypeProblem(ERROR, Object, "Validation error")
validationContext.createContextFor(JobType, true).visitTypeProblem(ERROR, Object, "Validation error")
}
0 * _
}
Expand All @@ -68,15 +68,14 @@ class ValidateStepTest extends ContextInsensitiveStepSpec {

then:
def ex = thrown WorkValidationException
ex.message == "Some problems were found with the configuration of job ':test'."
ex.message == "Some problems were found with the configuration of job ':test' (types 'ValidateStepTest.JobType', 'ValidateStepTest.SecondaryJobType')."
ex.causes.size() == 2
ex.causes[0].message == "Type '$Object.name': Validation error #1."
ex.causes[1].message == "Type '$Object.name': Validation error #2."
ex.causes[0].message == "Type '$Object.simpleName': Validation error #1."
ex.causes[1].message == "Type '$Object.simpleName': Validation error #2."

_ * work.validate(_ as WorkValidationContext) >> { WorkValidationContext validationContext ->
def typeContext = validationContext.createContextFor(Object, true)
typeContext.visitTypeProblem(ERROR, Object, "Validation error #1")
typeContext.visitTypeProblem(ERROR, Object, "Validation error #2")
validationContext.createContextFor(JobType, true).visitTypeProblem(ERROR, Object, "Validation error #1")
validationContext.createContextFor(SecondaryJobType, true).visitTypeProblem(ERROR, Object, "Validation error #2")
}
0 * _
}
Expand All @@ -87,11 +86,11 @@ class ValidateStepTest extends ContextInsensitiveStepSpec {

then:
_ * work.validate(_ as WorkValidationContext) >> { WorkValidationContext validationContext ->
validationContext.createContextFor(Object, true).visitTypeProblem(WARNING, Object, "Validation warning")
validationContext.createContextFor(JobType, true).visitTypeProblem(WARNING, Object, "Validation warning")
}

then:
1 * warningReporter.reportValidationWarning("Type '$Object.name': Validation warning.")
1 * warningReporter.reportValidationWarning("Type '$Object.simpleName': Validation warning.")

then:
1 * delegate.execute(context)
Expand All @@ -104,19 +103,23 @@ class ValidateStepTest extends ContextInsensitiveStepSpec {

then:
_ * work.validate(_ as WorkValidationContext) >> { WorkValidationContext validationContext ->
def typeContext = validationContext.createContextFor(Object, true)
def typeContext = validationContext.createContextFor(JobType, true)
typeContext.visitTypeProblem(ERROR, Object, "Validation error")
typeContext.visitTypeProblem(WARNING, Object, "Validation warning")
}

then:
1 * warningReporter.reportValidationWarning("Type '$Object.name': Validation warning.")
1 * warningReporter.reportValidationWarning("Type '$Object.simpleName': Validation warning.")

then:
def ex = thrown WorkValidationException
ex.message == "A problem was found with the configuration of job ':test'."
ex.message == "A problem was found with the configuration of job ':test' (type 'ValidateStepTest.JobType')."
ex.causes.size() == 1
ex.causes[0].message == "Type '$Object.name': Validation error."
ex.causes[0].message == "Type '$Object.simpleName': Validation error."
0 * _
}

interface JobType {}

interface SecondaryJobType {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ class SomeTask extends DefaultTask {
fails("consumer")

then:
failure.assertHasDescription("A problem was found with the configuration of task ':consumer'.")
failure.assertHasDescription("A problem was found with the configuration of task ':consumer' (type 'ConsumerTask').")
failure.assertHasCause("No value has been specified for property 'bean.inputFile'.")
failure.assertTasksExecuted(':consumer')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationSpec {
expect:
fails "custom"

failure.assertHasDescription("Some problems were found with the configuration of task ':custom'.")
failure.assertHasDescription("Some problems were found with the configuration of task ':custom' (type 'CustomTask').")
failure.assertHasCause("No value has been specified for property 'srcFile'.")
failure.assertHasCause("No value has been specified for property 'destFile'.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.gradle.internal.reflect;

import org.gradle.model.internal.type.ModelType;

import javax.annotation.Nullable;

abstract public class MessageFormattingTypeValidationContext implements TypeValidationContext {
Expand All @@ -30,7 +32,7 @@ public void visitTypeProblem(Severity severity, Class<?> type, String message) {
@SuppressWarnings("StringBufferReplaceableByString")
StringBuilder builder = new StringBuilder();
builder.append("Type '");
builder.append(type.getTypeName());
builder.append(ModelType.of(type).getDisplayName());
builder.append("': ");
builder.append(message);
builder.append('.');
Expand All @@ -42,7 +44,7 @@ public void visitPropertyProblem(Severity severity, @Nullable String parentPrope
StringBuilder builder = new StringBuilder();
if (rootType != null) {
builder.append("Type '");
builder.append(rootType.getTypeName());
builder.append(ModelType.of(rootType).getDisplayName());
builder.append("': ");
}
if (property != null) {
Expand Down
Loading

0 comments on commit 084f011

Please sign in to comment.