Tools and libraries to glue C/C++ APIs to high-level languages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

516 lines
24 KiB

using CodingSeb.ExpressionEvaluator;
using CppSharp.AST;
using CppSharp.Generators.CSharp;
using CppSharp.Passes;
using NUnit.Framework;
using System.Collections;
using System.Linq;
namespace CppSharp.Generator.Tests.Passes
{
[TestFixture]
public class TestPasses : ASTTestFixture
{
[SetUp]
public void Setup()
{
ParseLibrary("Passes.h");
passBuilder = new PassBuilder<TranslationUnitPass>(Driver.Context);
}
[TearDown]
public void TearDown()
{
Driver.Dispose();
}
[Test]
public void TestExtractInterfacePass()
{
var c = AstContext.Class("TestExtractInterfacePass");
Assert.IsNull(c.GetInterface());
passBuilder.AddPass(new ExtractInterfacePass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsNotNull(c.GetInterface());
Assert.AreEqual("ITestExtractInterfacePass", c.GetInterface().Name);
}
[Test]
public void TestCheckEnumsPass()
{
var @enum = AstContext.Enum("FlagEnum");
var enum2 = AstContext.Enum("FlagEnum2");
var boolClassEnum = AstContext.Enum("BoolEnum");
var ucharClassEnum = AstContext.Enum("UCharEnum");
Assert.IsFalse(@enum.IsFlags);
Assert.IsFalse(enum2.IsFlags);
Assert.IsFalse(boolClassEnum.IsFlags);
Assert.IsFalse(ucharClassEnum.IsFlags);
Assert.IsTrue(boolClassEnum.BuiltinType.Type == PrimitiveType.Bool);
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
passBuilder.AddPass(new CheckEnumsPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsTrue(@enum.IsFlags);
Assert.IsFalse(enum2.IsFlags);
Assert.IsFalse(boolClassEnum.IsFlags);
Assert.IsTrue(ucharClassEnum.IsFlags);
Assert.IsTrue(boolClassEnum.BuiltinType.Type != PrimitiveType.Bool, "C# does not support Bool enums");
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
}
[Test]
public void TestCheckStaticClassPass()
{
var staticClass = AstContext.Class("TestCheckStaticClass");
var staticStruct = AstContext.Class("TestCheckStaticStruct");
var staticClassDeletedCtor = AstContext.Class("TestCheckStaticClassDeleted");
var nonStaticClass = AstContext.Class("TestCheckNonStaticClass");
var nonStaticEmptyClass = AstContext.Class("TestCommentsPass");
Assert.IsFalse(staticClass.IsStatic);
Assert.IsFalse(staticStruct.IsStatic);
Assert.IsFalse(staticClassDeletedCtor.IsStatic);
Assert.IsFalse(nonStaticClass.IsStatic);
Assert.IsFalse(nonStaticEmptyClass.IsStatic);
passBuilder.AddPass(new CheckStaticClassPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsTrue(staticClass.IsStatic, "`TestCheckStaticClass` should be static");
Assert.IsTrue(staticStruct.IsStatic, "`TestCheckStaticStruct` should be static");
Assert.IsTrue(staticClassDeletedCtor.IsStatic, "`TestCheckStaticClassDeleted` should be static");
Assert.IsFalse(nonStaticClass.IsStatic, "`TestCheckNonStaticClass` should NOT be static, since it has a private data field with default ctor");
Assert.IsFalse(nonStaticEmptyClass.IsStatic, "`TestCommentsPass` should NOT be static, since it doesn't have any static declarations");
}
[Test]
public void TestFunctionToInstancePass()
{
var c = AstContext.Class("Foo");
Assert.IsNull(c.Method("Start"));
passBuilder.AddPass(new FunctionToInstanceMethodPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsNotNull(c.Method("Start"));
}
[Test]
public void TestFunctionToStaticPass()
{
var c = AstContext.Class("Foo");
Assert.IsTrue(AstContext.Function("FooStart").IsGenerated);
Assert.IsNull(c.Method("Start"));
passBuilder.AddPass(new FunctionToStaticMethodPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsFalse(AstContext.Function("FooStart").IsGenerated);
Assert.IsNotNull(c.Method("Start"));
}
[Test]
public void TestCleanCommentsPass()
{
var c = AstContext.Class("TestCommentsPass");
var c2 = AstContext.Class("TestCommentsPass2");
passBuilder.AddPass(new CleanCommentsPass());
passBuilder.RunPasses(pass => pass.VisitDeclaration(c));
passBuilder.RunPasses(pass => pass.VisitClassDecl(c2));
var para = (ParagraphComment)c.Comment.FullComment.Blocks[0];
var textGenerator = new TextGenerator();
textGenerator.Print(para, CommentKind.BCPLSlash);
Assert.That(textGenerator.StringBuilder.ToString().Trim(),
Is.EqualTo("/// <summary>A simple test.</summary>"));
var textGenerator2 = new TextGenerator();
textGenerator2.Print(c2.Methods[0].Comment.FullComment, CommentKind.BCPLSlash);
Assert.That(textGenerator2.StringBuilder.ToString().Trim(),
Is.EqualTo(
"/// <summary>Gets a value</summary>\n" +
"/// <returns>One</returns>"
));
var textGenerator3 = new TextGenerator();
textGenerator3.Print(c2.Methods[1].Comment.FullComment, CommentKind.BCPLSlash);
Assert.That(textGenerator3.StringBuilder.ToString().Trim(),
Is.EqualTo(
"/// <summary>Sets a value. Get it with <see cref=\"GetValueWithComment\"/></summary>\n" +
"/// <param name=\"value\">The value to set</param>\n" +
"/// <returns>The parameter (typeof<float>)</returns>"
));
}
[Test]
public void TestCaseRenamePass()
{
Type.TypePrinterDelegate += TypePrinterDelegate;
var c = AstContext.Class("TestRename");
passBuilder.AddPass(new FieldToPropertyPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var method = c.Method("lowerCaseMethod");
var property = c.Properties.Find(p => p.Name == "lowerCaseField");
passBuilder.RenameDeclsUpperCase(RenameTargets.Any);
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.That(method.Name, Is.EqualTo("LowerCaseMethod"));
Assert.That(property.Name, Is.EqualTo("LowerCaseField"));
Type.TypePrinterDelegate -= TypePrinterDelegate;
}
[Test]
public void TestCleanEnumItemNames()
{
AstContext.GenerateEnumFromMacros("TestEnumItemName", "TEST_ENUM_ITEM_NAME_(.*)");
var @enum = AstContext.Enum("TestEnumItemName");
Assert.IsNotNull(@enum);
// Testing the values read for the enum
Assert.AreEqual(3, @enum.Items[0].Value); // Decimal literal
Assert.AreEqual(0, @enum.Items[1].Value); // Hex literal
Assert.AreEqual(1, @enum.Items[2].Value); // Hex literal with suffix
Assert.AreEqual(2, @enum.Items[3].Value); // Enum item
passBuilder.RemovePrefix("TEST_ENUM_ITEM_NAME_", RenameTargets.EnumItem);
passBuilder.AddPass(new CleanInvalidDeclNamesPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.That(@enum.Items[1].Name, Is.EqualTo("_0"));
}
[Test]
public void TestEnumBaseTypesFromMacroValues()
{
AstContext.GenerateEnumFromMacros("TestEnumMaxSbyte", "TEST_ENUM_MAX_SBYTE");
var @enum = AstContext.Enum("TestEnumMaxSbyte");
Assert.IsNotNull(@enum);
Assert.AreEqual(sbyte.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxByte", "TEST_ENUM_MAX_BYTE");
@enum = AstContext.Enum("TestEnumMaxByte");
Assert.IsNotNull(@enum);
Assert.AreEqual(byte.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxShort", "TEST_ENUM_MAX_SHORT");
@enum = AstContext.Enum("TestEnumMaxShort");
Assert.IsNotNull(@enum);
Assert.AreEqual(short.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Short, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUshort", "TEST_ENUM_MAX_USHORT");
@enum = AstContext.Enum("TestEnumMaxUshort");
Assert.IsNotNull(@enum);
Assert.AreEqual(ushort.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UShort, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxInt", "TEST_ENUM_MAX_INT");
@enum = AstContext.Enum("TestEnumMaxInt");
Assert.IsNotNull(@enum);
Assert.AreEqual(int.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Int, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxLong", "TEST_ENUM_MAX_LONG");
@enum = AstContext.Enum("TestEnumMaxLong");
Assert.IsNotNull(@enum);
Assert.AreEqual(long.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUint", "TEST_ENUM_MAX_UINT");
@enum = AstContext.Enum("TestEnumMaxUint");
Assert.IsNotNull(@enum);
Assert.AreEqual(uint.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.UInt, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMaxUlong", "TEST_ENUM_MAX_ULONG");
@enum = AstContext.Enum("TestEnumMaxUlong");
Assert.IsNotNull(@enum);
Assert.AreEqual(ulong.MaxValue, @enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.ULongLong, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMinInt", "TEST_ENUM_MIN_INT");
@enum = AstContext.Enum("TestEnumMinInt");
Assert.IsNotNull(@enum);
Assert.AreEqual(int.MinValue, (long)@enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.Int, @enum.BuiltinType.Type);
AstContext.GenerateEnumFromMacros("TestEnumMinLong", "TEST_ENUM_MIN_LONG");
@enum = AstContext.Enum("TestEnumMinLong");
Assert.IsNotNull(@enum);
Assert.AreEqual(long.MinValue, (long)@enum.Items[0].Value);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
}
// These tests have nothing to do with testing passes except that the expression evaluator
// is only used by GenerateEnumFromMacros. Add a separate TestFixture?
static ExprEvalTestParams[] TestExpressionEvaluatorTestCases =
{
new ExprEvalTestParams("2147483647", int.MaxValue),
new ExprEvalTestParams("-2147483648", int.MinValue),
new ExprEvalTestParams("4294967295", uint.MaxValue),
new ExprEvalTestParams("9223372036854775807", long.MaxValue),
new ExprEvalTestParams("-9223372036854775808", long.MinValue),
new ExprEvalTestParams("18446744073709551615", ulong.MaxValue),
// Must use "L" suffix here or the expression evaluator will overflow with no warning. Note
// that it won't compile in C# without it.
new ExprEvalTestParams("-2147483648L - 1", -2147483648L - 1),
// Note that the dynamic subsystem used by the expression evaluator types this result as
// long, but the compiler types the expression a uint. Adding the casts to dynamic in
// the expressions below matches the result of the expression evaluator. Not sure we
// care that they're different.
new ExprEvalTestParams("2147483648 + 5", (dynamic)2147483648 + (dynamic)5),
new ExprEvalTestParams("5 + 2147483648", (dynamic)5 + (dynamic)2147483648),
new ExprEvalTestParams("0x2A828670572C << 1", 0x2A828670572C << 1),
new ExprEvalTestParams("-0xFC84D76B0482", -0xFC84D76B0482),
new ExprEvalTestParams("27 - 9223372036854775807", 27 - 9223372036854775807),
new ExprEvalTestParams("9223372036854775807", 9223372036854775807),
new ExprEvalTestParams("18446744073709551615", 18446744073709551615),
new ExprEvalTestParams("1 << 5", 1 << 5),
new ExprEvalTestParams("1 << 32", 1 << 32),
new ExprEvalTestParams("1L << 32", 1L << 32),
new ExprEvalTestParams("5u", 5u),
new ExprEvalTestParams("5ul", 5ul),
new ExprEvalTestParams("\"This is\" + \" a string expression\"", "This is" + " a string expression"),
new ExprEvalTestParams("(17 - 48 + 80 * 81 - 88) + (74 + 79 - 50) - ((76 - 51 - 88 + (9 + 98 - 47)))", (17 - 48 + 80 * 81 - 88) + (74 + 79 - 50) - ((76 - 51 - 88 + (9 + 98 - 47)))),
new ExprEvalTestParams("3.14159265", 3.14159265),
new ExprEvalTestParams("16 - 47 * (75 / 91) + 44", 16 - 47 * (75 / 91) + 44), // Does C# truncate the same way?
new ExprEvalTestParams("16 - 47 * (75d / 91d) + 44", 16 - 47 * (75d / 91d) + 44),
new ExprEvalTestParams("69d / 5 - 48 - 47 / (82d - 71 + 2 + 6 / 39d) - 56", 69d / 5 - 48 - 47 / (82d - 71 + 2 + 6 / 39d) - 56),
new ExprEvalTestParams("55.59m", 55.59m),
new ExprEvalTestParams("55.59m + 23", 55.59m + 23),
new ExprEvalTestParams("'A'", 'A'),
new ExprEvalTestParams("'A'+'B'", 'A' + 'B'),
new ExprEvalTestParams("(int)'A'", (int)'A'),
// C++ specific sufixes not supported. C++ octal not supported.
//new EETestParams("5ll", 5L),
//new EETestParams("5ull", 5UL),
//new EETestParams("016", 8 + 6),
new ExprEvalTestParams("V1 | V2", 3, ("V1", 1), ("V2", 2)),
new ExprEvalTestParams("TRUE && FALSE", false, ("TRUE", true), ("FALSE", false)),
};
class ExprEvalTestParams
{
public readonly string Expression;
public readonly object ExpectedResult;
public readonly (string Name, object Value)[] Symbols;
public ExprEvalTestParams(string expression, object expectedResult, params (string Name, object Value)[] symbols)
{
Expression = expression;
ExpectedResult = expectedResult;
Symbols = symbols;
}
}
static IEnumerable GetExpressionEvaluatorTestCases()
{
var testNumber = 100;
foreach (var tc in TestExpressionEvaluatorTestCases)
{
// "testNumber" is just used to cause the test runner to show the tests in the order
// we've listed them which is simply a development time convenience.
yield return new TestCaseData(testNumber++, tc.Expression, tc.ExpectedResult, tc.Symbols);
}
}
[TestCaseSource(nameof(GetExpressionEvaluatorTestCases))]
public void TestExpressionEvaluator(int testNumber, string expression, object expectedResult, (string SymbolName, object SymbolValue)[] symbols)
{
var evaluator = symbols == null || symbols.Length == 0
? new ExpressionEvaluator()
: new ExpressionEvaluator(symbols.ToDictionary(s => s.SymbolName, s => s.SymbolValue));
var result = evaluator.Evaluate(expression);
Assert.AreEqual(expectedResult, result);
Assert.AreEqual(expectedResult.GetType(), result.GetType());
}
[Test]
public void TestEnumsWithBitwiseExpressionMacroValues()
{
var @enum = AstContext.GenerateEnumFromMacros("TestBitwiseShift", "TEST_BITWISE_SHIFT_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
Assert.AreEqual(0x2A828670572C << 1, (long)@enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestNegativeHex", "TEST_NEGATIVE_HEX_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.LongLong, @enum.BuiltinType.Type);
Assert.AreEqual(-0xFC84D76B0482, (long)@enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseOr", "TEST_BITWISE_OR_1");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F | 0x80, @enum.Items[0].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseAnd", "TEST_BITWISE_AND_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F & 0xFF, @enum.Items[0].Value);
Assert.AreEqual(0x73 & -1, @enum.Items[1].Value);
Assert.AreEqual(0x42 & ~0x2, @enum.Items[2].Value);
@enum = AstContext.GenerateEnumFromMacros("TestBitwiseXor", "TEST_BITWISE_XOR_(.*)");
Assert.IsNotNull(@enum);
Assert.AreEqual(PrimitiveType.UChar, @enum.BuiltinType.Type);
Assert.AreEqual(0x7F ^ 0x03, @enum.Items[0].Value);
}
[Test]
public void TestUnnamedEnumSupport()
{
passBuilder.AddPass(new CleanInvalidDeclNamesPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var unnamedEnum1 = AstContext.FindEnum("Unnamed_Enum_1").Single();
var unnamedEnum2 = AstContext.FindEnum("Unnamed_Enum_2").Single();
Assert.IsNotNull(unnamedEnum1);
Assert.IsNotNull(unnamedEnum2);
Assert.AreEqual(2, unnamedEnum1.Items.Count);
Assert.AreEqual(2, unnamedEnum2.Items.Count);
Assert.AreEqual(1, unnamedEnum1.Items[0].Value);
Assert.AreEqual(2, unnamedEnum1.Items[1].Value);
Assert.AreEqual(3, unnamedEnum2.Items[0].Value);
Assert.AreEqual(4, unnamedEnum2.Items[1].Value);
}
[Test, Ignore("Nameless enums are no longer uniquely named.")]
public void TestUniqueNamesAcrossTranslationUnits()
{
passBuilder.AddPass(new CleanInvalidDeclNamesPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var unnamedEnum1 = AstContext.GetEnumWithMatchingItem("UnnamedEnumA1");
var unnamedEnum2 = AstContext.GetEnumWithMatchingItem("UnnamedEnumB1");
Assert.IsNotNull(unnamedEnum1);
Assert.IsNotNull(unnamedEnum2);
Assert.AreNotEqual(unnamedEnum1.Name, unnamedEnum2.Name);
}
[Test]
public void TestSkippedPrivateMethod()
{
AstContext.IgnoreClassMethodWithName("Foo", "toIgnore");
Assert.That(AstContext.FindClass("Foo").First().Methods.Find(
m => m.Name == "toIgnore"), Is.Null);
}
[Test]
public void TestSetPropertyAsReadOnly()
{
const string className = "TestReadOnlyProperties";
passBuilder.AddPass(new FieldToPropertyPass());
passBuilder.AddPass(new GetterSetterToPropertyPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
AstContext.SetPropertyAsReadOnly(className, "readOnlyProperty");
Assert.IsFalse(AstContext.FindClass(className).First().Properties.Find(
m => m.Name == "readOnlyProperty").HasSetter);
AstContext.SetPropertyAsReadOnly(className, "readOnlyPropertyMethod");
Assert.IsFalse(AstContext.FindClass(className).First().Properties.Find(
m => m.Name == "readOnlyPropertyMethod").HasSetter);
}
[Test]
public void TestCheckAmbiguousFunctionsPass()
{
passBuilder.AddPass(new CheckAmbiguousFunctions());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var @class = AstContext.FindClass("TestCheckAmbiguousFunctionsPass").FirstOrDefault();
Assert.IsNotNull(@class);
var overloads = @class.Methods.Where(m => m.Name == "Method");
var constMethod = overloads
.FirstOrDefault(m => m.IsConst && m.Parameters.Count == 0);
var nonConstMethod = overloads
.FirstOrDefault(m => !m.IsConst && m.Parameters.Count == 0);
Assert.IsNotNull(constMethod);
Assert.IsNotNull(nonConstMethod);
Assert.IsTrue(constMethod.GenerationKind == GenerationKind.None);
Assert.IsTrue(nonConstMethod.GenerationKind == GenerationKind.Generate);
var constMethodWithParam = overloads
.FirstOrDefault(m => m.IsConst && m.Parameters.Count == 1);
var nonConstMethodWithParam = overloads
.FirstOrDefault(m => !m.IsConst && m.Parameters.Count == 1);
Assert.IsNotNull(constMethodWithParam);
Assert.IsNotNull(nonConstMethodWithParam);
Assert.IsTrue(constMethodWithParam.GenerationKind == GenerationKind.None);
Assert.IsTrue(nonConstMethodWithParam.GenerationKind == GenerationKind.Generate);
}
[Test]
public void TestSetMethodAsInternal()
{
var c = AstContext.Class("TestMethodAsInternal");
var method = c.Method("beInternal");
Assert.AreEqual(method.Access, AccessSpecifier.Public);
passBuilder.AddPass(new CheckMacroPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.AreEqual(method.Access, AccessSpecifier.Internal);
}
private string TypePrinterDelegate(Type type)
{
return type.Visit(new CSharpTypePrinter(Driver.Context)).Type;
}
[Test]
public void TestAbstractOperator()
{
passBuilder.AddPass(new ValidateOperatorsPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var @class = AstContext.FindDecl<Class>("ClassWithAbstractOperator").First();
Assert.AreEqual(@class.Operators.First().GenerationKind, GenerationKind.None);
}
[Test]
public void TestFlattenAnonymousTypesToFields()
{
passBuilder.AddPass(new FlattenAnonymousTypesToFields());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
var @class = AstContext.FindDecl<Class>("TestFlattenAnonymousTypesToFields").First();
/* TODO: Enable this test and fix the parsing bug
var @public = @class.Fields.Where(f => f.Name == "Public").FirstOrDefault();
Assert.IsNotNull(@public);
Assert.AreEqual(AccessSpecifier.Public, @public.Access); */
var @protected = @class.Fields.Find(f => f.Name == "Protected");
Assert.IsNotNull(@protected);
Assert.AreEqual(AccessSpecifier.Protected, @protected.Access);
}
private PassBuilder<TranslationUnitPass> passBuilder;
}
}