5 changed files with 347 additions and 16 deletions
@ -0,0 +1,205 @@
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// ABIComparer.cs
|
||||
//
|
||||
// Author:
|
||||
// Mike Krüger <mkrueger@xamarin.com>
|
||||
//
|
||||
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.NRefactory.CSharp |
||||
{ |
||||
/// <summary>
|
||||
/// Used to check the compatibility state of two compilations.
|
||||
/// </summary>
|
||||
public enum AbiCompatibility |
||||
{ |
||||
/// <summary>
|
||||
/// The ABI is equal
|
||||
/// </summary>
|
||||
Equal, |
||||
|
||||
/// <summary>
|
||||
/// Some items got added, but the ABI remains to be compatible
|
||||
/// </summary>
|
||||
Bigger, |
||||
|
||||
/// <summary>
|
||||
/// The ABI has changed
|
||||
/// </summary>
|
||||
Incompatible |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The Abi comparer checks the public API of two compilation and determines the compatibility state.
|
||||
/// </summary>
|
||||
public class AbiComparer |
||||
{ |
||||
void CheckContstraints(ITypeParameter p1, ITypeParameter p2, ref AbiCompatibility compatibility) |
||||
{ |
||||
if (p1.DirectBaseTypes.Count () != p2.DirectBaseTypes.Count () || |
||||
p1.HasReferenceTypeConstraint != p2.HasReferenceTypeConstraint || |
||||
p1.HasValueTypeConstraint != p2.HasValueTypeConstraint || |
||||
p1.HasDefaultConstructorConstraint != p2.HasDefaultConstructorConstraint) { |
||||
IncompatibilityFound ("Constraints are not compatible."); |
||||
compatibility = AbiCompatibility.Incompatible; |
||||
} |
||||
} |
||||
|
||||
void CheckTypes (ITypeDefinition oType, ITypeDefinition nType, ref AbiCompatibility compatibility) |
||||
{ |
||||
int oldMemberCount = 0; |
||||
Predicate<IUnresolvedMember> pred = null; |
||||
if (oType.Kind == TypeKind.Class || oType.Kind == TypeKind.Struct) |
||||
pred = m => (m.IsPublic || m.IsProtected) && !m.IsOverride && !m.IsSynthetic; |
||||
|
||||
for (int i = 0; i < oType.TypeParameterCount; i++) { |
||||
CheckContstraints (oType.TypeParameters[i], nType.TypeParameters[i], ref compatibility); |
||||
if (compatibility == AbiCompatibility.Incompatible) |
||||
return; |
||||
} |
||||
|
||||
foreach (var member in oType.GetMembers (pred, GetMemberOptions.IgnoreInheritedMembers)) { |
||||
var newMember = nType.GetMembers (m => member.UnresolvedMember.Name == m.Name && m.IsPublic == member.IsPublic && m.IsProtected == member.IsProtected); |
||||
var equalMember = newMember.FirstOrDefault (m => SignatureComparer.Ordinal.Equals (member, m)); |
||||
if (equalMember == null) { |
||||
compatibility = AbiCompatibility.Incompatible; |
||||
return; |
||||
} |
||||
var om = member as IMethod; |
||||
if (om != null) { |
||||
for (int i = 0; i < om.TypeParameters.Count; i++) { |
||||
CheckContstraints (om.TypeParameters[i], ((IMethod)equalMember).TypeParameters[i], ref compatibility); |
||||
if (compatibility == AbiCompatibility.Incompatible) |
||||
return; |
||||
} |
||||
} |
||||
|
||||
oldMemberCount++; |
||||
} |
||||
if (compatibility == AbiCompatibility.Bigger && oType.Kind != TypeKind.Interface) |
||||
return; |
||||
if (oldMemberCount != nType.GetMembers (pred, GetMemberOptions.IgnoreInheritedMembers).Count ()) { |
||||
if (oType.Kind == TypeKind.Interface) { |
||||
IncompatibilityFound ("Interface " + oType.FullName + " changed."); |
||||
compatibility = AbiCompatibility.Incompatible; |
||||
} else { |
||||
compatibility = AbiCompatibility.Bigger; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void CheckNamespace(INamespace oNs, INamespace nNs, ref AbiCompatibility compatibility) |
||||
{ |
||||
foreach (var type in oNs.Types) { |
||||
if (!type.IsPublic && !type.IsProtected) |
||||
continue; |
||||
var newType = nNs.GetTypeDefinition (type.Name, type.TypeParameterCount); |
||||
if (newType == null) { |
||||
IncompatibilityFound ("Type definition "+ type.FullName +" is missing."); |
||||
compatibility = AbiCompatibility.Incompatible; |
||||
return; |
||||
} |
||||
CheckTypes (type, newType, ref compatibility); |
||||
if (compatibility == AbiCompatibility.Incompatible) |
||||
return; |
||||
} |
||||
|
||||
if (compatibility == AbiCompatibility.Bigger) |
||||
return; |
||||
foreach (var type in nNs.Types) { |
||||
if (!type.IsPublic && !type.IsProtected) |
||||
continue; |
||||
if (oNs.GetTypeDefinition (type.Name, type.TypeParameterCount) == null) { |
||||
compatibility = AbiCompatibility.Bigger; |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static bool ContainsPublicTypes(INamespace testNs) |
||||
{ |
||||
var stack = new Stack<INamespace> (); |
||||
stack.Push (testNs); |
||||
while (stack.Count > 0) { |
||||
var ns = stack.Pop (); |
||||
if (ns.Types.Any (t => t.IsPublic)) |
||||
return true; |
||||
foreach (var child in ns.ChildNamespaces) |
||||
stack.Push (child); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Check the specified oldProject and newProject if they're compatible.
|
||||
/// </summary>
|
||||
/// <param name="oldProject">Old project.</param>
|
||||
/// <param name="newProject">New project.</param>
|
||||
public AbiCompatibility Check (ICompilation oldProject, ICompilation newProject) |
||||
{ |
||||
var oldStack = new Stack<INamespace> (); |
||||
var newStack = new Stack<INamespace> (); |
||||
oldStack.Push (oldProject.MainAssembly.RootNamespace); |
||||
newStack.Push (newProject.MainAssembly.RootNamespace); |
||||
|
||||
AbiCompatibility compatibility = AbiCompatibility.Equal; |
||||
while (oldStack.Count > 0) { |
||||
var oNs = oldStack.Pop (); |
||||
var nNs = newStack.Pop (); |
||||
|
||||
CheckNamespace (oNs, nNs, ref compatibility); |
||||
if (compatibility == AbiCompatibility.Incompatible) |
||||
return AbiCompatibility.Incompatible; |
||||
foreach (var child in oNs.ChildNamespaces) { |
||||
var newChild = nNs.GetChildNamespace (child.Name); |
||||
if (newChild == null) { |
||||
IncompatibilityFound ("Namespace "+ child.FullName +" is missing."); |
||||
return AbiCompatibility.Incompatible; |
||||
} |
||||
oldStack.Push (child); |
||||
newStack.Push (newChild); |
||||
} |
||||
|
||||
// check if namespaces are added
|
||||
if (compatibility != AbiCompatibility.Bigger) { |
||||
foreach (var child in nNs.ChildNamespaces) { |
||||
if (oNs.GetChildNamespace (child.Name) == null) { |
||||
if (ContainsPublicTypes (child)) |
||||
compatibility = AbiCompatibility.Bigger; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return compatibility; |
||||
} |
||||
|
||||
void IncompatibilityFound(string str) |
||||
{ |
||||
Console.WriteLine (str); |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// AbiComparerTests.cs
|
||||
//
|
||||
// Author:
|
||||
// Mike Krüger <mkrueger@xamarin.com>
|
||||
//
|
||||
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using ICSharpCode.NRefactory.CSharp.Resolver; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||
using NUnit.Framework; |
||||
using ICSharpCode.NRefactory.CSharp.CodeCompletion; |
||||
|
||||
namespace ICSharpCode.NRefactory.CSharp.Analysis |
||||
{ |
||||
[TestFixture] |
||||
public class AbiComparerTests |
||||
{ |
||||
AbiCompatibility Check (string before, string after) |
||||
{ |
||||
IProjectContent oldPctx, newPctx; |
||||
SyntaxTree tree; |
||||
ICSharpCode.NRefactory.CSharp.TypeSystem.CSharpUnresolvedFile file; |
||||
CodeCompletionBugTests.CreateCompilation (before, out oldPctx, out tree, out file, false); |
||||
CodeCompletionBugTests.CreateCompilation (after, out newPctx, out tree, out file, false); |
||||
return new AbiComparer ().Check (oldPctx.CreateCompilation (), newPctx.CreateCompilation ()); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckEquality() |
||||
{ |
||||
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} public int Bar2 { get; set;} int removed; } class Removed {} }"; |
||||
string a2 = @"namespace Foo { public class Bar { public void FooBar () {} public int Bar2 { get {} set{}} void Added () {} } class Added {} } namespace Added { class Test { } }"; |
||||
Assert.AreEqual (AbiCompatibility.Equal, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckBigger() |
||||
{ |
||||
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} } }"; |
||||
string a2 = @"namespace Foo { public class Bar { public void FooBar () {} public void BarFoo () {} } }"; |
||||
Assert.AreEqual (AbiCompatibility.Bigger, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckIncompatible() |
||||
{ |
||||
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} } }"; |
||||
string a2 = @"namespace Foo { public class Bar { public void FooBar (int bar) {} } }"; |
||||
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckIncompatibleInterfaceChange() |
||||
{ |
||||
string a1 = @"public interface IFoo {}"; |
||||
string a2 = @"public interface IFoo { void Bar (); }"; |
||||
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckTypeConstraintChange() |
||||
{ |
||||
string a1 = @"public class IFoo<T> {}"; |
||||
string a2 = @"public class IFoo<T> where T : System.IDisposable {}"; |
||||
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckTypeConstraintChangeCase2() |
||||
{ |
||||
string a1 = @"public class IFoo<T> {}"; |
||||
string a2 = @"public class IFoo<T> where T : class {}"; |
||||
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||
} |
||||
|
||||
[Test] |
||||
public void CheckMethodConstraintChange() |
||||
{ |
||||
string a1 = @"public class IFoo { public void Bar<T> () {} }"; |
||||
string a2 = @"public class IFoo { public void Bar<T> () where T : System.IDisposable {} }"; |
||||
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue