blob: 478360fc317c5e4ba042a71c48d757b086343510 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Apache.Ignite.Core.Tests.ApiParity
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Apache.Ignite.Core.Impl.Common;
using NUnit.Framework;
/// <summary>
/// Base class for API parity tests.
/// </summary>
public static class ParityTest
{
/** Test ignore reason: we should not fail builds due to new APIs being added in Java. */
public const string IgnoreReason = "API parity tests are supposed to be run manually.";
/** Property regex. */
private static readonly Regex JavaPropertyRegex =
new Regex("(@Deprecated)?\\s+public [^=^\r^\n]+ (\\w+)\\(\\) {", RegexOptions.Compiled);
/** Interface method regex. */
private static readonly Regex JavaInterfaceMethodRegex =
new Regex("(@Deprecated)?\\s+(@Override)?\\s+public [^=^\r^\n]+ (\\w+)\\(.*?\\)",
RegexOptions.Compiled | RegexOptions.Singleline);
/** Properties that are not needed on .NET side. */
private static readonly string[] UnneededMethods =
{
"toString",
"hashCode",
"writeReplace"
};
/// <summary>
/// Tests the configuration parity.
/// </summary>
public static void CheckConfigurationParity(string javaFilePath,
Type type,
IEnumerable<string> excludedProperties = null,
IEnumerable<string> knownMissingProperties = null,
Dictionary<string, string> knownMappings = null)
{
var path = GetFullPath(javaFilePath);
var dotNetProperties = type.GetProperties()
.ToDictionary(x => x.Name, x => (MemberInfo) x, StringComparer.OrdinalIgnoreCase);
var javaProperties = GetJavaProperties(path)
.Except(excludedProperties ?? Enumerable.Empty<string>());
CheckParity(type, knownMissingProperties, knownMappings, javaProperties, dotNetProperties);
}
/// <summary>
/// Tests the configuration parity.
/// </summary>
public static void CheckInterfaceParity(string javaFilePath,
Type type,
IEnumerable<string> excludedMembers = null,
IEnumerable<string> knownMissingMembers = null,
Dictionary<string, string> knownMappings = null)
{
var path = GetFullPath(javaFilePath);
var dotNetMembers = GetMembers(type)
.GroupBy(x => x.Name)
.ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase);
var javaMethods = GetJavaInterfaceMethods(path)
.Except(excludedMembers ?? Enumerable.Empty<string>());
CheckParity(type, knownMissingMembers, knownMappings, javaMethods, dotNetMembers);
}
/// <summary>
/// Gets the members.
/// </summary>
private static IEnumerable<MemberInfo> GetMembers(Type type)
{
var types = new Stack<Type>();
types.Push(type);
while (types.Count > 0)
{
var t = types.Pop();
foreach (var m in t.GetMembers())
{
yield return m;
}
foreach (var i in t.GetInterfaces())
{
types.Push(i);
}
}
}
/// <summary>
/// Gets the full path.
/// </summary>
private static string GetFullPath(string javaFilePath)
{
javaFilePath = javaFilePath.Replace('\\', Path.DirectorySeparatorChar);
var path = Path.Combine(IgniteHome.Resolve(), javaFilePath);
Assert.IsTrue(File.Exists(path), path);
return path;
}
/// <summary>
/// Checks the parity.
/// </summary>
private static void CheckParity(Type type, IEnumerable<string> knownMissingMembers,
IDictionary<string, string> knownMappings, IEnumerable<string> javaMethods,
IDictionary<string, MemberInfo> dotNetMembers)
{
var missingMembers = javaMethods
.Where(jp => !GetNameVariants(jp, knownMappings).Any(dotNetMembers.ContainsKey))
.ToDictionary(x => x, x => x, StringComparer.OrdinalIgnoreCase);
var knownMissing = (knownMissingMembers ?? Enumerable.Empty<string>())
.ToDictionary(x => x, x => x, StringComparer.OrdinalIgnoreCase);
var sb = new StringBuilder();
var codeSb = new StringBuilder();
foreach (var javaMissingProp in missingMembers)
{
if (!knownMissing.ContainsKey(javaMissingProp.Key))
{
sb.AppendFormat("{0}.{1} member is missing in .NET.\n" +
"For new functionality please file a .NET ticket and update MissingProperties " +
"array accordingly with a link to that ticket.\n", type.Name, javaMissingProp.Key);
codeSb.AppendFormat("\"{0}\", ", javaMissingProp.Key);
}
}
foreach (var dotnetMissingProp in knownMissing)
{
if (!missingMembers.ContainsKey(dotnetMissingProp.Key))
{
sb.AppendFormat("{0}.{1} member is missing in Java, but is specified as known in .NET.\n",
type.Name, dotnetMissingProp.Key);
}
}
if (sb.Length > 0)
{
Assert.Fail(sb + "\nQuoted list: " + codeSb);
}
}
/// <summary>
/// Gets the java properties from file.
/// </summary>
private static IEnumerable<string> GetJavaProperties(string path)
{
var text = File.ReadAllText(path);
// ReSharper disable once RedundantEnumerableCastCall
return JavaPropertyRegex.Matches(text)
.OfType<Match>()
.Where(m => m.Groups[1].Value == string.Empty)
.Select(m => m.Groups[2].Value.Replace("get", ""))
.Where(x => !x.Contains(" void "))
.Except(UnneededMethods);
}
/// <summary>
/// Gets the java interface methods from file.
/// </summary>
private static IEnumerable<string> GetJavaInterfaceMethods(string path)
{
var text = File.ReadAllText(path);
// ReSharper disable once RedundantEnumerableCastCall
return JavaInterfaceMethodRegex.Matches(text)
.OfType<Match>()
.Where(m => m.Groups[1].Value == string.Empty)
.Select(m => m.Groups[3].Value.Replace("get", ""))
.Except(UnneededMethods);
}
/// <summary>
/// Gets the name variants for a property.
/// </summary>
private static IEnumerable<string> GetNameVariants(string javaPropertyName,
IDictionary<string, string> knownMappings)
{
yield return javaPropertyName;
yield return "get" + javaPropertyName;
yield return "is" + javaPropertyName;
yield return javaPropertyName.Replace("PoolSize", "ThreadPoolSize");
if (javaPropertyName.StartsWith("is"))
{
yield return javaPropertyName.Substring(2);
}
string map;
if (knownMappings != null && knownMappings.TryGetValue(javaPropertyName, out map))
{
yield return map;
}
}
}
}