Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify why convention tests are failing #908

Merged
merged 1 commit into from
Sep 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace Octokit.Tests.Conventions
{
public class InvalidDebuggerDisplayAttributeValueException : Exception
{
public InvalidDebuggerDisplayAttributeValueException(Type modelType, string value)
: base (CreateMessage(modelType, value)) { }

static string CreateMessage(Type modelType, string value)
{
return string.Format(
"Model type '{0}' has invalid DebuggerDisplayAttribute value '{1}'. Expected '{{DebuggerDisplay, nq}}'",
modelType.FullName,
value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace Octokit.Tests.Conventions
{
public class InvalidDebuggerDisplayReturnType : Exception
{
public InvalidDebuggerDisplayReturnType(Type modelType, Type propertyType)
: base (CreateMessage(modelType, propertyType)) { }

static string CreateMessage(Type modelType, Type propertyType)
{
return string.Format(
"Model type '{0}' has invalid DebuggerDisplay return type '{1}'. Expected 'string'.",
modelType.FullName,
propertyType.Name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Octokit.Tests.Conventions
{
public class MissingDebuggerDisplayAttributeException : Exception
{
public MissingDebuggerDisplayAttributeException(Type modelType)
: base (string.Format("Model type '{0}' is missing the DebuggerDisplayAttribute.", modelType.FullName)) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Octokit.Tests.Conventions
{
public class MissingDebuggerDisplayPropertyException : Exception
{
public MissingDebuggerDisplayPropertyException(Type modelType)
: base (string.Format("Model type '{0}' is missing the DebuggerDisplay property.", modelType.FullName)) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Octokit.Tests.Conventions
{
public class MutableModelPropertiesException : Exception
{
public MutableModelPropertiesException(Type modelType, IEnumerable<PropertyInfo> mutableProperties)
: base (CreateMessage(modelType, mutableProperties)) { }

static string CreateMessage(Type modelType, IEnumerable<PropertyInfo> mutableProperties)
{
return string.Format("Model type '{0}' contains the following mutable properties: {1}{2}",
modelType.FullName,
Environment.NewLine,
string.Join(Environment.NewLine, mutableProperties.Select(x => x.Name)));
}
}
}
50 changes: 43 additions & 7 deletions Octokit.Tests.Conventions/ModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections;
using System.Diagnostics;
using System.Linq;
using Octokit.Tests.Helpers;
using Xunit;
using System.Collections.Generic;
using System.Reflection;
Expand All @@ -15,32 +14,59 @@ public class ModelTests
[MemberData("ModelTypes")]
public void AllModelsHaveDebuggerDisplayAttribute(Type modelType)
{
var attribute = AssertEx.HasAttribute<DebuggerDisplayAttribute>(modelType);
var attribute = modelType.GetCustomAttribute<DebuggerDisplayAttribute>(inherit: false);
if (attribute == null)
{
throw new MissingDebuggerDisplayAttributeException(modelType);
}

Assert.Equal("{DebuggerDisplay,nq}", attribute.Value);
if (attribute.Value != "{DebuggerDisplay,nq}")
{
throw new InvalidDebuggerDisplayAttributeValueException(modelType, attribute.Value);
}

var property = modelType.GetProperty("DebuggerDisplay", BindingFlags.Instance | BindingFlags.NonPublic);
if (property == null)
{
throw new MissingDebuggerDisplayPropertyException(modelType);
}

Assert.NotNull(property);
Assert.Equal(typeof(string), property.PropertyType);
if (property.PropertyType != typeof(string))
{
throw new InvalidDebuggerDisplayReturnType(modelType, property.PropertyType);
}
}

[Theory]
[MemberData("ResponseModelTypes")]
public void ResponseModelsHaveGetterOnlyProperties(Type modelType)
{
var mutableProperties = new List<PropertyInfo>();

foreach (var property in modelType.GetProperties())
{
var setter = property.GetSetMethod(nonPublic: true);

Assert.True(setter == null || !setter.IsPublic);
if (setter == null || !setter.IsPublic)
{
continue;
}

mutableProperties.Add(property);
}

if (mutableProperties.Any())
{
throw new MutableModelPropertiesException(modelType, mutableProperties);
}
}

[Theory]
[MemberData("ResponseModelTypes")]
public void ResponseModelsHaveReadOnlyCollections(Type modelType)
{
var mutableCollectionProperties = new List<PropertyInfo>();

foreach (var property in modelType.GetProperties())
{
var propertyType = property.PropertyType;
Expand All @@ -54,9 +80,19 @@ public void ResponseModelsHaveReadOnlyCollections(Type modelType)
continue;
}

AssertEx.IsReadOnlyCollection(propertyType);
if (propertyType.IsReadOnlyCollection())
{
continue;
}

mutableCollectionProperties.Add(property);
}
}

if (mutableCollectionProperties.Any())
{
throw new MutableModelPropertiesException(modelType, mutableCollectionProperties);
}
}

//TODO: This should (probably) be moved to the PaginationTests class that is being introduced in PR #760
Expand Down
5 changes: 5 additions & 0 deletions Octokit.Tests.Conventions/Octokit.Tests.Conventions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Exception\InvalidDebuggerDisplayAttributeValueException.cs" />
<Compile Include="Exception\InvalidDebuggerDisplayReturnType.cs" />
<Compile Include="Exception\MissingDebuggerDisplayAttributeException.cs" />
<Compile Include="Exception\MissingDebuggerDisplayPropertyException.cs" />
<Compile Include="Exception\MutableModelPropertiesException.cs" />
<Compile Include="Exception\PaginationGetAllMethodNameMismatchException.cs" />
<Compile Include="ModelTests.cs" />
<Compile Include="Exception\InterfaceHasAdditionalMethodsException.cs" />
Expand Down
14 changes: 14 additions & 0 deletions Octokit.Tests.Conventions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ public static Type GetGenericArgument(this Type type)
{
return type.GetGenericArguments()[0];
}

public static bool IsReadOnlyCollection(this Type type)
{
var isReadOnlyList = type.HasGenericTypeDefinition(typeof(IReadOnlyList<>));

var isReadOnlyDictionary = type.HasGenericTypeDefinition(typeof(IReadOnlyDictionary<,>));

return isReadOnlyList || isReadOnlyDictionary;
}

private static bool HasGenericTypeDefinition(this Type type, Type genericTypeDefinition)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == genericTypeDefinition;
}
}

public enum TypeCategory { Other, Task, GenericTask, ReadOnlyList, ClientInterface }
Expand Down
19 changes: 0 additions & 19 deletions Octokit.Tests/Helpers/AssertEx.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -14,15 +13,6 @@ public static void WithMessage(Action assert, string message)
assert();
}

public static TAttribute HasAttribute<TAttribute>(MemberInfo memberInfo, bool inherit = false) where TAttribute : Attribute
{
var attribute = memberInfo.GetCustomAttribute<TAttribute>(inherit);

Assert.NotNull(attribute);

return attribute;
}

static readonly string[] whitespaceArguments = { " ", "\t", "\n", "\n\r", " " };

public static async Task ThrowsWhenGivenWhitespaceArgument(Func<string, Task> action)
Expand All @@ -39,14 +29,5 @@ public static void IsReadOnlyCollection<T>(object instance)
// The collection == null case is for .NET 4.0
Assert.True(instance is IReadOnlyList<T> && (collection == null || collection.IsReadOnly));
}

public static void IsReadOnlyCollection(Type type)
{
var isReadOnlyList = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IReadOnlyList<>);

var isReadOnlyDictionary = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>);

Assert.True(isReadOnlyList || isReadOnlyDictionary);
}
}
}