Browse Source

LINQ-to-Profiler: fixed crash when there is a ThenBy in the query.

CodeCoverage: set COMPLUS_ProfAPI_ProfilerCompatibilitySetting to run PartCover on .NET 4.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5034 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
0991e23416
  1. 31
      src/AddIns/Misc/CodeCoverage/Project/Src/PartCoverRunner.cs
  2. 6
      src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs
  3. 33
      src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
  4. 86
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
  5. 15
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs
  6. 48
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs
  7. 3
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Profiler.Tests.csproj
  8. 10
      src/Main/Base/Project/Src/Util/ProcessRunner.cs

31
src/AddIns/Misc/CodeCoverage/Project/Src/PartCoverRunner.cs

@ -26,7 +26,7 @@ namespace ICSharpCode.CodeCoverage @@ -26,7 +26,7 @@ namespace ICSharpCode.CodeCoverage
StringCollection include = new StringCollection();
StringCollection exclude = new StringCollection();
string output = String.Empty;
/// <summary>
/// Triggered when PartCover exits.
/// </summary>
@ -38,7 +38,7 @@ namespace ICSharpCode.CodeCoverage @@ -38,7 +38,7 @@ namespace ICSharpCode.CodeCoverage
public event EventHandler Started;
/// <summary>
/// The PartCover runner was stopped. Being stopped is not the
/// The PartCover runner was stopped. Being stopped is not the
/// same as PartCover exiting.
/// </summary>
public event EventHandler Stopped;
@ -53,7 +53,7 @@ namespace ICSharpCode.CodeCoverage @@ -53,7 +53,7 @@ namespace ICSharpCode.CodeCoverage
}
/// <summary>
/// Gets or sets the full path to the PartCover
/// Gets or sets the full path to the PartCover
/// executable.
/// </summary>
public string PartCoverFileName {
@ -95,7 +95,7 @@ namespace ICSharpCode.CodeCoverage @@ -95,7 +95,7 @@ namespace ICSharpCode.CodeCoverage
}
/// <summary>
/// Gets or sets the regular expressions which specify the items to
/// Gets or sets the regular expressions which specify the items to
/// include in the report whilst profiling the target executable.
/// </summary>
public StringCollection Include {
@ -103,7 +103,7 @@ namespace ICSharpCode.CodeCoverage @@ -103,7 +103,7 @@ namespace ICSharpCode.CodeCoverage
}
/// <summary>
/// Gets or sets the regular expressions which specify the items to
/// Gets or sets the regular expressions which specify the items to
/// exclude in the report whilst profiling the target executable.
/// </summary>
public StringCollection Exclude {
@ -116,7 +116,7 @@ namespace ICSharpCode.CodeCoverage @@ -116,7 +116,7 @@ namespace ICSharpCode.CodeCoverage
public string Output {
get { return output; }
set { output = value; }
}
}
/// <summary>
/// Returns the full path used to run PartCover.
@ -141,9 +141,9 @@ namespace ICSharpCode.CodeCoverage @@ -141,9 +141,9 @@ namespace ICSharpCode.CodeCoverage
/// so in order for this to be passed to PartCover as a single argument
/// we need to prefix each double quote by a backslash. For example:
///
/// Target args: "C:\Projects\My Tests\Test.dll" /output "C:\Projects\My Tests\Output.xml"
/// Target args: "C:\Projects\My Tests\Test.dll" /output "C:\Projects\My Tests\Output.xml"
///
/// PartCover: --target-args "\"C:\Projects\My Tests\Test.dll\" /output \"C:\Projects\My Tests\Output.xml\""
/// PartCover: --target-args "\"C:\Projects\My Tests\Test.dll\" /output \"C:\Projects\My Tests\Output.xml\""
/// </remarks>
public string GetArguments()
{
@ -170,15 +170,16 @@ namespace ICSharpCode.CodeCoverage @@ -170,15 +170,16 @@ namespace ICSharpCode.CodeCoverage
}
arguments.Append(GetArguments("--exclude", exclude));
return arguments.ToString().Trim();
}
}
public void Start()
{
{
string arguments = GetArguments();
runner = new ProcessRunner();
runner.EnvironmentVariables.Add("COMPLUS_ProfAPI_ProfilerCompatibilitySetting", "EnableV2Profiler");
runner.WorkingDirectory = workingDirectory;
runner.ProcessExited += ProcessExited;
@ -186,7 +187,7 @@ namespace ICSharpCode.CodeCoverage @@ -186,7 +187,7 @@ namespace ICSharpCode.CodeCoverage
runner.OutputLineReceived += OnOutputLineReceived;
runner.ErrorLineReceived += OnOutputLineReceived;
}
runner.Start(partCoverFileName, arguments);
runner.Start(partCoverFileName, arguments);
OnStarted();
}
@ -241,10 +242,10 @@ namespace ICSharpCode.CodeCoverage @@ -241,10 +242,10 @@ namespace ICSharpCode.CodeCoverage
/// <param name="e">The event arguments.</param>
void ProcessExited(object sender, EventArgs e)
{
ProcessRunner runner = (ProcessRunner)sender;
ProcessRunner runner = (ProcessRunner)sender;
OnExited(runner.StandardOutput, runner.StandardError, runner.ExitCode);
}
}
/// <summary>
/// Gets the command line option that can have multiple items as specified
/// in the string array. Each array item will have a separate command line

6
src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs

@ -30,7 +30,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -30,7 +30,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public static readonly MethodInfo QueryableOfCallTreeNode_Take = MethodOf((IQueryable<CallTreeNode> q) => q.Take(1));
public static readonly MethodInfo Queryable_OrderBy = MethodOf((IQueryable<CallTreeNode> q) => q.OrderBy(x => x)).GetGenericMethodDefinition();
public static readonly MethodInfo Queryable_OrderByDesc = MethodOf((IQueryable<CallTreeNode> q) => q.OrderByDescending(x => x)).GetGenericMethodDefinition();
public static readonly MethodInfo Queryable_ThenBy = MethodOf((IOrderedQueryable<CallTreeNode> q) => q.ThenBy(x => x)).GetGenericMethodDefinition();
public static readonly MethodInfo Queryable_ThenBy2 = MethodOf((IOrderedQueryable<CallTreeNode> q) => q.ThenBy(x => x, null)).GetGenericMethodDefinition();
public static readonly MethodInfo Queryable_ThenByDesc = MethodOf((IOrderedQueryable<CallTreeNode> q) => q.ThenByDescending(x => x)).GetGenericMethodDefinition();
public static readonly MethodInfo Queryable_ThenByDesc2 = MethodOf((IOrderedQueryable<CallTreeNode> q) => q.ThenByDescending(x => x, null)).GetGenericMethodDefinition();
public static readonly MethodInfo String_StartsWith = MethodOf((string s) => s.StartsWith(s, default(StringComparison)));
public static readonly MethodInfo String_EndsWith = MethodOf((string s) => s.EndsWith(s, default(StringComparison)));
public static readonly MethodInfo Queryable_Merge = MethodOf((IQueryable<CallTreeNode> q) => q.Merge());
public static readonly MethodInfo Queryable_MergeByName = MethodOf((IQueryable<CallTreeNode> q) => q.MergeByName());

33
src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs

@ -335,9 +335,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -335,9 +335,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
struct SortArgument {
LambdaExpression arg;
bool desc;
class SortArgument
{
readonly LambdaExpression arg;
readonly bool desc;
public SortArgument(LambdaExpression arg, bool desc)
{
@ -351,18 +352,21 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -351,18 +352,21 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public LambdaExpression Argument {
get { return arg; }
}
public override string ToString()
{
if (Descending)
return Argument.ToString() + " DESC";
else
return Argument.ToString();
}
}
sealed class Sort : QueryNode
{
ReadOnlyCollection<SortArgument> arguments;
public Sort(QueryNode target, LambdaExpression argument, bool desc)
: this(target, new[] { new SortArgument(argument, desc) })
{
}
Sort(QueryNode target, IList<SortArgument> args)
public Sort(QueryNode target, IList<SortArgument> args)
: base(target)
{
this.arguments = new ReadOnlyCollection<SortArgument>(args);
@ -407,11 +411,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -407,11 +411,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public override string ToString()
{
StringBuilder builder = new StringBuilder();
foreach (var item in arguments)
builder.Append(item.Argument + " " + (item.Descending ? "DESC" : "ASC") + ",");
return Target + ".Sort( {" + builder.ToString() + "} )";
for (int i = 0; i < arguments.Count; i++) {
if (i > 0)
builder.Append(", ");
builder.Append(arguments[i].ToString());
}
return Target + ".Sort(" + builder.ToString() + ")";
}
}
}

86
src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

@ -178,7 +178,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -178,7 +178,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// Optimizes the query without executing it.
/// Used for unit tests.
/// </summary>
public Expression OptimizeQuery(Expression inputExpression)
public static Expression OptimizeQuery(Expression inputExpression)
{
Expression partiallyEvaluatedExpression = PartialEvaluator.Eval(inputExpression, CanBeEvaluatedStatically);
Expression expression = new ConvertToQueryAstVisitor(new QueryExecutionOptions()).Visit(partiallyEvaluatedExpression);
@ -296,25 +296,79 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -296,25 +296,79 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
return new Limit(target, 0, (int)ce.Value);
}
}
} else if ((IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderBy) ||
IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderByDesc)) &&
node.Arguments[1].NodeType == ExpressionType.Quote) {
} else if (IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderBy) ||
IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderByDesc)) {
SortArgument sortArgument = GetSortArgumentFromSortCall(node);
if (sortArgument != null) {
QueryNode target = Visit(node.Arguments[0]) as QueryNode;
if (target != null) {
return new Sort(target, new [] { sortArgument });
}
}
} else if (IsThenByCall(node)) {
// 'ThenBy' is dangerous: we must not translate an OrderBy inside a ThenBy that we do not support
List<MethodCallExpression> thenByCalls = new List<MethodCallExpression>();
Expression tmp = node;
while (IsThenByCall(tmp)) {
thenByCalls.Add((MethodCallExpression)tmp);
tmp = ((MethodCallExpression)tmp).Arguments[0];
}
MethodCallExpression mc = tmp as MethodCallExpression;
if (mc != null &&
(IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_OrderBy) ||
IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_OrderByDesc)))
{
// TODO: add support for safe expressions in ThenBy
// this is an unsupported OrderBy/ThenBy sequence
// skip visiting the sequence and go directly into the base object
tmp = Visit(mc.Arguments[0]);
// now reconstruct the OrderBy/ThenBy sequence
tmp = Expression.Call(mc.Method, new[] { tmp }.Concat(mc.Arguments.Skip(1))); // reconstruct OrderBy
for (int i = thenByCalls.Count - 1; i >= 0; i--) {
// reconstruct ThenBy
tmp = Expression.Call(thenByCalls[i].Method, new[] { tmp }.Concat(thenByCalls[i].Arguments.Skip(1)));
}
return tmp;
}
// else: we couldn't detect the OrderBy belonging to this ThenBy; so it's probably not one
// of the OrderBy overloads that we support. Go down recursively
}
return base.VisitMethodCall(node);
}
SortArgument GetSortArgumentFromSortCall(MethodCallExpression node)
{
if (node.Arguments[1].NodeType == ExpressionType.Quote) {
UnaryExpression quote = (UnaryExpression)node.Arguments[1];
if (quote.Operand.NodeType == ExpressionType.Lambda) {
LambdaExpression lambda = (LambdaExpression)quote.Operand;
if (lambda.Parameters.Count == 1) {
QueryNode target = Visit(node.Arguments[0]) as QueryNode;
if (target != null) {
SafeExpressionImporter importer = new SafeExpressionImporter(lambda.Parameters[0]);
Expression imported = importer.Import(lambda.Body);
if (imported != null)
return new Sort(target, Expression.Lambda(imported, lambda.Parameters[0]), IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderByDesc));
}
SafeExpressionImporter importer = new SafeExpressionImporter(lambda.Parameters[0]);
Expression imported = importer.Import(lambda.Body);
if (imported != null)
return new SortArgument(
Expression.Lambda(imported, lambda.Parameters[0]),
IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_OrderByDesc)
|| IsGenericMethodInfoOfCallTreeNode(node, KnownMembers.Queryable_ThenByDesc)
);
}
}
}
return base.VisitMethodCall(node);
return null;
}
bool IsThenByCall(Expression potentialCall)
{
MethodCallExpression mc = potentialCall as MethodCallExpression;
if (mc != null) {
return IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_ThenBy) ||
IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_ThenBy2) ||
IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_ThenByDesc) ||
IsGenericMethodInfoOfCallTreeNode(mc, KnownMembers.Queryable_ThenByDesc2);
} else {
return false;
}
}
}
#endregion
@ -409,7 +463,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -409,7 +463,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
MethodCallExpression mc = (MethodCallExpression)expr;
if (mc.Method == KnownMembers.String_StartsWith)
return CreateCall(mc, (pattern, wildcard) => pattern + wildcard);
return CreateStringMatchCall(mc, (pattern, wildcard) => pattern + wildcard);
if (mc.Method == KnownMembers.String_EndsWith)
return CreateStringMatchCall(mc, (pattern, wildcard) => wildcard + pattern);
return null;
}
@ -442,7 +498,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -442,7 +498,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
Expression CreateCall(MethodCallExpression mc, Func<string, string, string> expressionOf)
Expression CreateStringMatchCall(MethodCallExpression mc, Func<string, string, string> expressionOf)
{
Expression imported = Import(mc.Object);
if (imported != null && mc.Arguments[0].NodeType == ExpressionType.Constant && mc.Arguments[1].NodeType == ExpressionType.Constant) {

15
src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs

@ -32,7 +32,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -32,7 +32,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
sealed class CallTreeNodeSqlNameSet
{
public readonly string ID;
public readonly string NameID = "nameid";
public readonly string NameID;
public readonly string CpuCyclesSpent;
public readonly string CallCount;
public readonly string HasChildren;
@ -48,15 +48,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -48,15 +48,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
this.IsCalls = isCalls;
string prefix = c.GenerateUniqueVariableName();
if (isCalls) {
ID = "id";
CpuCyclesSpent = "cpucyclesspent";
CallCount = "callcount";
} else {
ID = prefix + "ID";
CpuCyclesSpent = prefix + "CpuCyclesSpent";
CallCount = prefix + "CallCount";
}
ID = prefix + "ID";
NameID = prefix + "NameID";
CpuCyclesSpent = prefix + "CpuCyclesSpent";
CallCount = prefix + "CallCount";
HasChildren = prefix + "HasChildren";
ActiveCallCount = prefix + "ActiveCallCount";
}

48
src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs

@ -143,5 +143,53 @@ namespace Profiler.Tests.Controller.Data @@ -143,5 +143,53 @@ namespace Profiler.Tests.Controller.Data
Assert.AreEqual(1, functions[1].CallCount);
Assert.AreEqual(350 * k, functions[1].CpuCyclesSpent);
}
[Test]
public void TestSupportedOrderByOnRootChildren()
{
CallTreeNode root = provider.GetRoot(0, 0);
var query = root.Children.OrderBy(f => f.CallCount);
Assert.AreEqual("AllCalls.Filter(c => (c.ParentID == 0)).MergeByName().Sort(f => f.CallCount)",
SQLiteQueryProvider.OptimizeQuery(query.Expression).ToString());
}
[Test]
public void TestOrderByNameMappingName()
{
CallTreeNode root = provider.GetRoot(0, 0);
var query = root.Children.OrderBy(f => f.NameMapping.Name);
Assert.AreEqual("AllCalls.Filter(c => (c.ParentID == 0)).MergeByName().Sort(f => f.NameMapping.Name)",
SQLiteQueryProvider.OptimizeQuery(query.Expression).ToString());
}
[Test]
public void TestOrderByWithUnsupporedThenBy()
{
CallTreeNode root = provider.GetRoot(0, 0);
var query = root.Children.OrderBy(f => f.CallCount).ThenBy(f => f.Ancestors.Count());
Assert.AreEqual("AllCalls.Filter(c => (c.ParentID == 0)).MergeByName()" +
".OrderBy(f => f.CallCount).ThenBy(f => f.Ancestors.Count())",
SQLiteQueryProvider.OptimizeQuery(query.Expression).ToString());
}
[Test]
public void TestOrderBySupportedThenByButUnsupportedConverter()
{
CallTreeNode root = provider.GetRoot(0, 0);
var query = root.Children.OrderBy(f => f.CallCount).ThenBy(f => f.NameMapping.Name, StringComparer.CurrentCultureIgnoreCase);
Assert.AreEqual("AllCalls.Filter(c => (c.ParentID == 0)).MergeByName()" +
".OrderBy(f => f.CallCount).ThenBy(f => f.NameMapping.Name, value(System.CultureAwareComparer))",
SQLiteQueryProvider.OptimizeQuery(query.Expression).ToString());
}
[Test]
public void TestOrderByWithMultiLevelUnsupporedThenBy()
{
CallTreeNode root = provider.GetRoot(0, 0);
var query = root.Children.OrderBy(f => f.CallCount).ThenBy(f => f.NameMapping.Name).ThenBy(f => f.Ancestors.Count());
Assert.AreEqual("AllCalls.Filter(c => (c.ParentID == 0)).MergeByName()" +
".OrderBy(f => f.CallCount).ThenBy(f => f.NameMapping.Name).ThenBy(f => f.Ancestors.Count())",
SQLiteQueryProvider.OptimizeQuery(query.Expression).ToString());
}
}
}

3
src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Profiler.Tests.csproj

@ -74,6 +74,9 @@ @@ -74,6 +74,9 @@
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
<ItemGroup>
<Reference Include="IQToolkit">
<HintPath>..\..\..\..\..\Libraries\IQToolkit\IQToolkit.dll</HintPath>
</Reference>
<Reference Include="nunit.framework">
<HintPath>..\..\..\..\..\..\bin\Tools\NUnit\nunit.framework.dll</HintPath>
</Reference>

10
src/Main/Base/Project/Src/Util/ProcessRunner.cs

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
@ -151,6 +152,12 @@ namespace ICSharpCode.SharpDevelop.Util @@ -151,6 +152,12 @@ namespace ICSharpCode.SharpDevelop.Util
}
}
Dictionary<string, string> environmentVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, string> EnvironmentVariables {
get { return environmentVariables; }
}
/// <summary>
/// Starts the process.
/// </summary>
@ -174,6 +181,9 @@ namespace ICSharpCode.SharpDevelop.Util @@ -174,6 +181,9 @@ namespace ICSharpCode.SharpDevelop.Util
process.StartInfo.UseShellExecute = false;
process.StartInfo.Arguments = arguments;
foreach (var pair in environmentVariables)
process.StartInfo.EnvironmentVariables.Add(pair.Key, pair.Value);
if (ProcessExited != null) {
process.EnableRaisingEvents = true;
process.Exited += OnProcessExited;

Loading…
Cancel
Save