This commit is contained in:
Zack Newman 2020-09-18 13:15:32 -07:00
Родитель aba940c914
Коммит 1b0319d3a6
473 изменённых файлов: 54680 добавлений и 10 удалений

31
.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1,31 @@
# Set default behavior to automatically normalize line endings.
* text=auto
*.jpg binary
*.png binary
*.gif binary
# Force bash scripts to always use lf line endings so that if a repo is accessed
# in Unix via a file share from Windows, the scripts will work.
*.in text eol=lf
*.sh text eol=lf
# Likewise, force cmd and batch scripts to always use crlf
*.cmd text eol=crlf
*.bat text eol=crlf
*.cs text=auto diff=csharp
*.css text=auto
*.htm text=auto
*.html text=auto
*.js text=auto
*.less text=auto
*.ps1 text=auto
*.py text=auto
*.resx text=auto
*.sass text=auto
*.scss text=auto
*.csproj text=auto
*.sln text=auto eol=crlf

14
CONTRIBUTING.md Normal file
Просмотреть файл

@ -0,0 +1,14 @@
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
and actually do, grant us the rights to use your contribution. For details, visit
https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

129
CustomDictionary.xml Normal file
Просмотреть файл

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<Words>
<Recognized>
<Word>Nt</Word>
<Word>Multitrace</Word>
<Word>Hwnd</Word>
<Word>Perf</Word>
<Word>dst</Word>
<Word>src</Word>
<Word>Gui</Word>
<Word>Hdv</Word>
<Word>Js</Word>
<Word>tti</Word>
<Word>Graphable</Word>
<Word>Datatable</Word>
<Word>Mem</Word>
<Word>Strided</Word>
<Word>Sym</Word>
<Word>Dir</Word>
<Word>XPerf</Word>
<Word>Hiber</Word>
<Word>Diag</Word>
<Word>Realloc</Word>
<Word>Decommit</Word>
<Word>Decommitting</Word>
<Word>Reaccess</Word>
<Word>Inlined</Word>
<Word>Pgoed</Word>
<Word>CWnd</Word>
<Word>SndWnd</Word>
<Word>Snd</Word> <!--Apparently the DiscreteExceptions don't help here-->
<Word>Wnd</Word>
<Word>Ack</Word>
<Word>Acked</Word>
<Word>Heuristical</Word>
<Word>Prefetcher</Word>
<Word>Alloc</Word>
<Word>Frag</Word>
<Word>Multi</Word>
<Word>SvcHost</Word>
<Word>Auth</Word>
<Word>msg</Word>
<Word>Multi</Word>
<Word>Proc</Word>
<Word>Utils</Word>
<!--Acronyms need to go in the recognized list as well-->
<Word>WPA</Word>
<Word>APD</Word>
<Word>GTI</Word>
<Word>HDV</Word>
<Word>GPU</Word>
<Word>DPC</Word>
<Word>ISR</Word>
<Word>CPU</Word>
<Word>PID</Word>
<Word>TID</Word>
<Word>IRQ</Word>
<Word>ETW</Word>
<Word>Bfs</Word>
<Word>Lfh</Word>
<Word>Pgo</Word>
<Word>Nt</Word>
<Word>Tcp</Word>
<Word>Tcb</Word>
<Word>Ip</Word>
<Word>Ws</Word>
<Word>Kpi</Word>
<Word>Tdh</Word>
<Word>WinProc</Word>
<Word>Pfn</Word>
<Word>Mru</Word>
<Word>Teb</Word>
<Word>IRP</Word>
</Recognized>
<DiscreteExceptions>
<Term>XPerf</Term>
<Term>DataTable</Term>
<Term>InTo</Term>
<Term>CWnd</Term>
<Term>SndWnd</Term>
<Term>ClsId</Term>
<Term>hWnd</Term>
<Term>WinProc</Term>
<Term>NonEmpty</Term>
<Term>CodeName</Term>
</DiscreteExceptions>
<Compound>
<Term>ToMin</Term>
<Term>SubType</Term>
</Compound>
</Words>
<Acronyms>
<CasingExceptions>
<Acronym>WPA</Acronym>
<Acronym>APD</Acronym>
<Acronym>GTI</Acronym>
<Acronym>HDV</Acronym>
<Acronym>GPU</Acronym>
<Acronym>DPC</Acronym>
<Acronym>ISR</Acronym>
<Acronym>CPU</Acronym>
<Acronym>PID</Acronym>
<Acronym>TID</Acronym>
<Acronym>IRQ</Acronym>
<Acronym>ETW</Acronym>
<Acronym>BFS</Acronym>
<Acronym>LFH</Acronym>
<Acronym>PGO</Acronym>
<Acronym>NT</Acronym>
<Acronym>TCP</Acronym>
<Acronym>TCB</Acronym>
<Acronym>IP</Acronym>
<Acronym>ID</Acronym>
<Acronym>AIFI</Acronym>
<Acronym>AIFO</Acronym>
<Acronym>AOFI</Acronym>
<Acronym>AOFO</Acronym>
<Acronym>WS</Acronym>
<Acronym>KPI</Acronym>
<Acronym>hWnd</Acronym>
<Acronym>TDH</Acronym>
<Acronym>Pfn</Acronym>
<Acronym>Mru</Acronym>
<Acronym>Teb</Acronym>
<Acronym>IRP</Acronym>
</CasingExceptions>
</Acronyms>
</Dictionary>

9
Directory.Build.props Normal file
Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning">
<Version>3.1.91</Version>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

Просмотреть файл

Просмотреть файл

@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Loader;
using System.Text;
namespace Microsoft.Performance.SDK.Runtime.NetCoreApp.Discovery
{
/// <summary>
/// Contains static methods related to AssemblyLoad functionality.
/// </summary>
public static class AssemblyLoadHelpers
{
/// <summary>
/// Find all conflicting DLLs in the current AppDomain.
/// </summary>
/// <returns>
/// Conflicting DLL paths, keyed by assembly name.
/// </returns>
public static IDictionary<string, IEnumerable<string>> FindDllConflicts()
{
var loadedDlls = new Dictionary<string, List<string>>();
var loaded = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in loaded.Where(x => !x.IsDynamic))
{
var name = assembly.GetName().Name;
var location = assembly.Location;
if (!loadedDlls.TryGetValue(name, out List<string> paths))
{
paths = new List<string>();
loadedDlls[name] = paths;
}
paths.Add(location);
}
var duplicates = loadedDlls.Where(x => x.Value.Count > 1)
.ToDictionary(k => k.Key, v => v.Value.AsEnumerable());
return duplicates;
}
internal static void EmitLoadContexts(
TextWriter output)
{
if (output is null)
{
return;
}
void printAssemblies(AssemblyLoadContext context, bool isForLastContext)
{
var assemblies = context.Assemblies.OrderBy(x => x.FullName).ToArray();
if (assemblies.Length == 0)
{
return;
}
var leftMostPipe = !isForLastContext ? '|' : ' ';
for (var j = 0; j < assemblies.Length - 1; ++j)
{
var assembly = assemblies[j];
output.WriteLine(@"{0} |-- {1}", leftMostPipe, assembly.FullName);
output.WriteLine(@"{0} | \-- {1}", leftMostPipe, assembly.GetCodeBaseAsLocalPath());
}
Debug.Assert(assemblies.Length > 0);
output.WriteLine(@"{0} \-- {1}", leftMostPipe, assemblies[assemblies.Length - 1].FullName);
output.WriteLine(@"{0} \-- {1}", leftMostPipe, assemblies[assemblies.Length - 1].GetCodeBaseAsLocalPath());
}
string contextName(AssemblyLoadContext context)
{
var n = new StringBuilder(context.Name?.Length ?? 0 + " (Default)".Length);
if (!string.IsNullOrWhiteSpace(context.Name))
{
n.Append(context.Name);
}
else
{
n.Append("<NO NAME>");
}
if (context == AssemblyLoadContext.Default)
{
n.Append(" (Default)");
}
return n.ToString();
}
var contexts = AssemblyLoadContext.All.OrderBy(x => x.Name).ToArray();
if (contexts.Length == 0)
{
return;
}
output.WriteLine("Assembly Load Contexts");
for (var i = 0; i < contexts.Length - 1; ++i)
{
var context = contexts[i];
output.WriteLine("|-- {0}", contextName(context));
printAssemblies(context, false);
}
Debug.Assert(contexts.Length > 0);
output.WriteLine(@"\-- {0}", contextName(contexts[contexts.Length - 1]));
printAssemblies(contexts[contexts.Length - 1], true);
}
}
}

Просмотреть файл

@ -0,0 +1,347 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Performance.SDK.Runtime.Discovery;
namespace Microsoft.Performance.SDK.Runtime.NetCoreApp.Discovery
{
/// <summary>
/// Used to load assemblies into isolated contexts.
/// </summary>
public sealed class IsolationAssemblyLoader
: IAssemblyLoader
{
/// <inheritdoc />
public bool SupportsIsolation => true;
// filename, name
private static readonly IReadOnlyDictionary<string, string> AlwaysSharedAssembliesFileNameToName
= new Dictionary<string, string>
{
{ "Microsoft.Performance.SDK.dll", "Microsoft.Performance.SDK" },
{ "Microsoft.Performance.SDK.Runtime.dll", "Microsoft.Performance.SDK.Runtime" },
{ "Microsoft.Performance.SDK.Runtime.NetCoreApp.dll", "Microsoft.Performance.SDK.Runtime.NetCoreApp" },
};
private static readonly Lazy<string> ApplicationBase = new Lazy<string>(
() => Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName),
System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
private static readonly Dictionary<string, AssemblyLoadContext> loadContexts =
new Dictionary<string, AssemblyLoadContext>();
// This is used for testing purposes only
internal static readonly IReadOnlyDictionary<string, AssemblyLoadContext> LoadContexts =
new ReadOnlyDictionary<string, AssemblyLoadContext>(loadContexts);
private readonly Dictionary<string, string> alwaysSharedAssembliesFileNameToName;
private readonly Dictionary<string, string> alwaysSharedAssembliesNameToFileName;
/// <summary>
/// Initializes a new instance of the <see cref="IsolationAssemblyLoader"/>
/// class.
/// </summary>
public IsolationAssemblyLoader()
: this(new Dictionary<string, string>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IsolationAssemblyLoader"/>
/// class, denoting any additional assemblies that must always be shered
/// between the enclosing application and plugins.
/// </summary>
/// <param name="additionalSharedAssembliesFileNameToName">
/// The additional assemblies, if any, are specified by dictionary mapping
/// the file name of the assembly (the file name with the extension) to
/// the name of the assembly. For example, { "Microsoft.Performance.SDK.dll", "Microsoft.Performance.SDK" }
/// This parameter may be <c>null</c>.
/// </param>
public IsolationAssemblyLoader(
IDictionary<string, string> additionalSharedAssembliesFileNameToName)
{
this.alwaysSharedAssembliesFileNameToName = new Dictionary<string, string>(AlwaysSharedAssembliesFileNameToName);
if (!(additionalSharedAssembliesFileNameToName is null))
{
foreach (var kvp in additionalSharedAssembliesFileNameToName)
{
this.alwaysSharedAssembliesFileNameToName.TryAdd(kvp.Key, kvp.Value);
}
}
this.alwaysSharedAssembliesNameToFileName = this.alwaysSharedAssembliesFileNameToName.ToDictionary(
x => x.Value,
x => x.Key);
}
/// <inheritdoc cref=""/>
public Assembly LoadAssembly(string assemblyPath)
{
if (!CliUtils.IsCliAssembly(assemblyPath))
{
return null;
}
var directory = Path.GetDirectoryName(assemblyPath);
var fileName = Path.GetFileName(assemblyPath);
Assembly loadedAssembly = SpeciallyLoadedByAssemblyFileName(fileName);
if (loadedAssembly == null)
{
if (!loadContexts.TryGetValue(directory, out var loadContext))
{
loadContext = new PluginAssemblyLoadContext(directory, this.SpeciallyLoadedByAssemblyName);
if (!loadContexts.TryAdd(directory, loadContext))
{
if (!loadContexts.TryGetValue(directory, out loadContext))
{
Console.Error.WriteLine($"Unable to load assembly {assemblyPath}.");
}
}
}
if (loadContext != null)
{
try
{
loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath);
}
catch (BadImageFormatException)
{
//
// this means it is native code or otherwise
// not readable by the CLR.
//
loadedAssembly = null;
}
catch (FileLoadException e)
{
Console.Error.WriteLine(
"[warn]: managed assembly `{0}` cannot be loaded - {1}.",
assemblyPath,
e.FusionLog);
loadedAssembly = null;
}
catch (FileNotFoundException)
{
loadedAssembly = null;
}
}
}
return loadedAssembly;
}
// This is used for testing purposes only
internal static void ClearLoadContexts()
{
foreach (var kvp in loadContexts)
{
try
{
kvp.Value.Unload();
}
catch (Exception)
{ }
}
loadContexts.Clear();
}
private Assembly SpeciallyLoadedByAssemblyName(string assemblyName)
{
if (!this.alwaysSharedAssembliesNameToFileName.TryGetValue(assemblyName, out var assemblyFileName))
{
return null;
}
return this.SpeciallyLoadedByAssemblyFileName(assemblyFileName);
}
private Assembly SpeciallyLoadedByAssemblyFileName(string fileName)
{
if (!this.alwaysSharedAssembliesFileNameToName.ContainsKey(fileName))
{
return null;
}
var loadedAssembly = AssemblyLoadContext.Default.Assemblies.SingleOrDefault(
x => !x.IsDynamic &&
StringComparer.OrdinalIgnoreCase.Equals(
Path.GetFileName(x.GetCodeBaseAsLocalPath()),
fileName));
if (loadedAssembly == null)
{
var assemblyPath = Path.Combine(ApplicationBase.Value, fileName);
Debug.Assert(File.Exists(assemblyPath));
loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
}
return loadedAssembly;
}
/// <summary>
/// This is used to find references to other modules from modules exposed within an
/// <see cref="IsolationAssemblyLoader"/>. If the referenced module doesn't already exist in the given context, the
/// AssemblyLoadContext will call <see cref="Load"/> to try to find it.
/// </summary>
private sealed class PluginAssemblyLoadContext
: AssemblyLoadContext
{
private readonly string pluginDirectory;
private readonly Func<string, Assembly> speciallyLoadedByAssemblyName;
// todo: I think we need to include *.deps.json for extensions to make sure the resolvers work correctly
// see: https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
private readonly List<AssemblyDependencyResolver> resolvers;
public PluginAssemblyLoadContext(
string directory,
Func<string, Assembly> speciallyLoadedByAssemblyName)
: base("Plugin: " + directory)
{
Guard.NotNullOrWhiteSpace(directory, nameof(directory));
Guard.NotNull(speciallyLoadedByAssemblyName, nameof(speciallyLoadedByAssemblyName));
this.pluginDirectory = directory;
this.speciallyLoadedByAssemblyName = speciallyLoadedByAssemblyName;
this.resolvers = new List<AssemblyDependencyResolver>();
//
// A plugin will only load dependencies from:
// - it's directory tree
// - the shared context.
//
// todo: when debugging, it looks like we get a lot of dups in this loop - worth some more investigation at some point
// I think *maybe* we only need to add resolvers if there is an associated *.deps.json file, and otherwise we'll fall
// back to the FindFileInDirectory approach?
var dlls = Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories);
foreach (var dll in dlls)
{
this.resolvers.Add(new AssemblyDependencyResolver(dll));
}
}
protected override Assembly Load(
AssemblyName assemblyName)
{
//
// Check if the requested assembly is a shared assembly.
//
Assembly loadedAssembly = this.speciallyLoadedByAssemblyName(assemblyName.Name);
if (loadedAssembly is null)
{
//
// Use the standard resolution rules.
//
foreach (var resolver in this.resolvers)
{
var path = resolver.ResolveAssemblyToPath(assemblyName);
if (path != null)
{
loadedAssembly = this.LoadFromAssemblyPath(path);
break;
}
}
//
// We didn't find the assembly anywhere, so probe the plugin's
// entire directory tree to see if we can find it in any of the
// subfolders.
//
if (loadedAssembly is null)
{
var name = assemblyName.Name;
var filePath = FindFileInDirectory(name, pluginDirectory);
if (!string.IsNullOrWhiteSpace(filePath))
{
loadedAssembly = this.LoadFromAssemblyPath(filePath);
}
}
}
return loadedAssembly;
}
protected override IntPtr LoadUnmanagedDll(
string unmanagedDllName)
{
//
// Use the standard resolution rules.
//
IntPtr found = IntPtr.Zero;
foreach (var resolver in this.resolvers)
{
var path = resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (path != null)
{
found = this.LoadUnmanagedDllFromPath(path);
break;
}
}
//
// We didn't find the DLL anywhere, so probe the plugin's
// entire directory tree to see if we can find it in any of the
// subfolders.
//
if (found == IntPtr.Zero)
{
var filePath = FindFileInDirectory(unmanagedDllName, pluginDirectory);
if (!string.IsNullOrWhiteSpace(filePath))
{
found = this.LoadUnmanagedDllFromPath(filePath);
}
}
return found;
}
private static string FindFileInDirectory(
string fileNameWithoutExtension,
string directory)
{
foreach (var filePath in Directory.GetFiles(directory))
{
var candidate = Path.GetFileNameWithoutExtension(filePath);
if (fileNameWithoutExtension == candidate)
{
return filePath;
}
}
foreach (var directoryPath in Directory.GetDirectories(directory))
{
var filePath = FindFileInDirectory(
fileNameWithoutExtension,
directoryPath);
if (!string.IsNullOrWhiteSpace(filePath))
{
return filePath;
}
}
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Microsoft.Performance.SDK.Runtime.NetCoreApp</AssemblyName>
<RootNamespace>Microsoft.Performance.SDK.Runtime.NetCoreApp</RootNamespace>
<Authors>Microsoft Corporation</Authors>
<Company>Microsoft Corporation</Company>
<Product>Performance ToolKit</Product>
<Description>Contains .NET Core specific components of the Performance Toolkit Runtime.</Description>
<Copyright>Copyright © 2020 Microsoft Corporation</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Performance.SDK.Runtime\Microsoft.Performance.SDK.Runtime.csproj" />
<ProjectReference Include="..\Microsoft.Performance.SDK\Microsoft.Performance.SDK.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Performance.SDK.Runtime.Tests")]
[assembly: InternalsVisibleTo("ProfileConverter")]

Просмотреть файл

@ -0,0 +1,825 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class CustomDataSourceExecutorTests
{
private CancellationTokenSource Cts { get; set; }
private CustomDataSourceExecutor Sut { get; set; }
[TestInitialize]
public void Initialize()
{
this.Cts = new CancellationTokenSource();
this.Sut = new CustomDataSourceExecutor();
}
[TestCleanup]
public void Cleanup()
{
this.Cts.Dispose();
}
[TestMethod]
[UnitTest]
public void ExecuteWithNullExecutionContextThrows()
{
Assert.ThrowsException<ArgumentNullException>(
() => this.Sut.InitializeCustomDataProcessor(null));
}
[TestMethod]
[UnitTest]
public void ExecuteSourceThatReturnsNullThrows()
{
var progress = new DataProcessorProgress();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = null,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
Assert.ThrowsException<InvalidOperationException>(
() => this.Sut.InitializeCustomDataProcessor(executionContext));
}
[TestMethod]
[UnitTest]
public void ExecutePassesEnvironmentToProcessor()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var env = Any.ProcessorEnvironment();
var options = ProcessorOptions.Default;
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
env,
options);
this.Sut.InitializeCustomDataProcessor(executionContext);
Assert.AreEqual(
env,
fakeCustomDataSource.CreateProcessorCalls.Single().Item2);
}
[TestMethod]
[UnitTest]
public void ExecutePassesOptionsToProcessor()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var options = ProcessorOptions.Default;
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
options);
this.Sut.InitializeCustomDataProcessor(executionContext);
Assert.AreEqual(
executionContext.CommandLineOptions,
fakeCustomDataSource.CreateProcessorCalls.Single().Item3);
}
[TestMethod]
[UnitTest]
public void ExecuteEnablesTheCorrectTables()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
Assert.AreEqual(tables.Length, mockProcessor.EnableTableCalls.Count);
foreach (var table in tables)
{
Assert.IsTrue(mockProcessor.EnableTableCalls.Contains(table));
}
}
[TestMethod]
[UnitTest]
public async Task ExecuteRequestedtablesSetCorrectly()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(tables.Length, result.RequestedTables.Count());
foreach (var table in tables)
{
Assert.IsTrue(result.RequestedTables.Contains(table));
}
}
[TestMethod]
[UnitTest]
public void ExecuteEnableTableFailureContinuesAnyway()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
mockProcessor.EnableFailures[tables[1]] = new Exception();
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
Assert.AreEqual(tables.Length, mockProcessor.EnableTableCalls.Count);
foreach (var table in tables)
{
Assert.IsTrue(mockProcessor.EnableTableCalls.Contains(table));
}
}
[TestMethod]
[UnitTest]
public async Task ExecuteEnableTableFailureNotedCorrectly()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
mockProcessor.EnableFailures[tables[1]] = new ArithmeticException();
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(mockProcessor.EnableFailures.Count, result.EnableFailures.Count);
foreach (var expectedFailure in mockProcessor.EnableFailures)
{
Assert.AreEqual(
expectedFailure.Value,
result.EnableFailures[expectedFailure.Key]);
}
}
[TestMethod]
[UnitTest]
public async Task ExecuteDoesNotBuildTables()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(0, mockProcessor.BuildTableCalls.Count);
}
[TestMethod]
[UnitTest]
public async Task ExecuteBuiltMetadataTablesReturned()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
MetadataTablesToBuild = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
}
};
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tablesToEnable = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tablesToEnable,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(mockProcessor.MetadataTablesToBuild.Count(), result.BuiltMetadataTables.Count);
foreach (var table in mockProcessor.MetadataTablesToBuild)
{
Assert.IsTrue(result.BuiltMetadataTables.Select(x => x.TableDescriptor).Contains(table));
}
}
[TestMethod]
[UnitTest]
public async Task ExecuteFailureToBuildMetadataTablesNoted()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
MetadataTablesToBuild = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
},
BuildMetadataTableFailure = new Exception(),
};
var fakeCustomDataSource = new MockCustomDataSource()
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tablesToEnable = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tablesToEnable,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(0, result.BuiltMetadataTables.Count);
Assert.AreEqual(mockProcessor.BuildMetadataTableFailure, result.MetadataTableFailure);
}
[TestMethod]
[UnitTest]
public async Task ExecuteDataSourceInfoSetCorrectly()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
DataSourceInfo = new DataSourceInfo(3, 4, DateTime.UnixEpoch),
};
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(mockProcessor.DataSourceInfo, result.DataSourceInfo);
}
[TestMethod]
[UnitTest]
public async Task ExecuteDataSourceInfoIsNullCoalescesIntoDefault()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
DataSourceInfo = null,
};
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(DataSourceInfo.Default, result.DataSourceInfo);
}
[TestMethod]
[UnitTest]
public async Task ExecuteDatSourceInfoFailureNoted()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
DataSourceInfoFailure = new Exception(),
};
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(DataSourceInfo.Default, result.DataSourceInfo);
Assert.AreEqual(mockProcessor.DataSourceInfoFailure, result.DataSourceInfoFailure);
}
[TestMethod]
[UnitTest]
public async Task ExecuteMetadataNameSetToSourceName()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(fakeCustomDataSource.TryGetName(), result.MetadataName);
}
[TestMethod]
[UnitTest]
public async Task ExecuteContextSetToArgument()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(executionContext, result.Context);
}
[TestMethod]
[UnitTest]
public async Task ExecuteCallsProcessCorrectly()
{
var progress = new DataProcessorProgress();
var logger = new NullLogger();
var mockProcessor = new MockCustomDataProcessor();
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => logger,
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(this.Cts.Token);
Assert.AreEqual(1, mockProcessor.ProcessCalls.Count);
Assert.AreEqual(progress, mockProcessor.ProcessCalls[0].Item1);
Assert.AreEqual(this.Cts.Token, mockProcessor.ProcessCalls[0].Item2);
}
[TestMethod]
[UnitTest]
public async Task ExecuteProcessorFailsSetsFaultProperties()
{
var progress = new DataProcessorProgress();
var mockProcessor = new MockCustomDataProcessor
{
ProcessFailure = new Exception(),
};
var fakeCustomDataSource = new MockCustomDataSource
{
DataProcessor = mockProcessor,
};
var dataSources = new[]
{
Any.DataSource(),
};
var tables = new[]
{
Any.TableDescriptor(),
};
var executionContext = new ExecutionContext(
progress,
_ => new NullLogger(),
fakeCustomDataSource,
dataSources,
tables,
Any.ProcessorEnvironment(),
ProcessorOptions.Default);
this.Sut.InitializeCustomDataProcessor(executionContext);
var result = await this.Sut.ExecuteAsync(CancellationToken.None);
Assert.IsNotNull(result);
Assert.AreEqual(DataSourceInfo.None, result.DataSourceInfo);
Assert.AreEqual(mockProcessor.ProcessFailure, result.ProcessorFault);
Assert.IsTrue(result.IsProcessorFaulted);
}
[CustomDataSource("{D6E5DC8D-E19D-4E55-99D9-746813C55A97}", "Test", "Test")]
private sealed class MockCustomDataSource
: ICustomDataSource
{
public MockCustomDataSource()
{
this.CreateProcessorCalls = new List<Tuple<IEnumerable<IDataSource>, IProcessorEnvironment, ProcessorOptions>>();
}
public ICustomDataProcessor DataProcessor { get; set; }
public IEnumerable<TableDescriptor> DataTables { get; }
public IEnumerable<TableDescriptor> MetadataTables { get; }
public IEnumerable<Option> CommandLineOptions => Enumerable.Empty<Option>();
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
return this.CreateProcessor(
new[] { dataSource, },
processorEnvironment,
options);
}
public List<Tuple<IEnumerable<IDataSource>, IProcessorEnvironment, ProcessorOptions>> CreateProcessorCalls { get; }
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
this.CreateProcessorCalls.Add(Tuple.Create(dataSources, processorEnvironment, options));
return this.DataProcessor;
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -0,0 +1,320 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DataSourceAttribute = Microsoft.Performance.SDK.Processing.DataSourceAttribute;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class CustomDataSourceReferenceTests
{
[TestMethod]
[UnitTest]
public void TryCreateReferenceForNullTypeThrows()
{
Assert.ThrowsException<ArgumentNullException>(
() => CustomDataSourceReference.TryCreateReference(
null,
out CustomDataSourceReference _));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForNoAttributesFails()
{
RunCreateFailTest(typeof(CdsWithNoAttributes));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForMissingCdsAttributeFails()
{
RunCreateFailTest(typeof(CdsWithoutCdsAttribute));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForMissingDataSourceAttributeFails()
{
RunCreateFailTest(typeof(CdsWithoutDataSourceAttribute));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForNoInterfaceImplementationFails()
{
RunCreateFailTest(typeof(CdsWithoutInterface));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForInaccessibleConstructorFails()
{
RunCreateFailTest(typeof(CdsWithInaccessibleConstructor));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceWithoutParameterlessConstructorFails()
{
RunCreateFailTest(typeof(CdsWithParameterizedConstructor));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceForInaccessibleTypeFails()
{
RunCreateFailTest(typeof(ProtectedType));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceNominal()
{
RunCreateSuccessTest(typeof(SampleCds));
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceNestedPublicSucceeds()
{
RunCreateSuccessTest(typeof(NestedPublicType));
}
[TestMethod]
[UnitTest]
public void CloneClones()
{
var result = CustomDataSourceReference.TryCreateReference(
typeof(SampleCds),
out CustomDataSourceReference reference);
Assert.IsTrue(result);
Assert.IsNotNull(reference);
var clone = reference.CloneT();
Assert.IsNotNull(clone);
Assert.AreEqual(reference.AssemblyPath, clone.AssemblyPath);
Assert.AreEqual(reference.DataSource, clone.DataSource);
Assert.AreEqual(reference.Description, clone.Description);
Assert.AreEqual(reference.Guid, clone.Guid);
Assert.AreEqual(reference.Name, clone.Name);
Assert.AreEqual(reference.Type, clone.Type);
Assert.IsTrue(reference.AvailableTables.All(x => clone.AvailableTables.Contains(x)));
Assert.IsTrue(clone.AvailableTables.All(x => reference.AvailableTables.Contains(x)));
}
private static void RunCreateSuccessTest(Type type)
{
var result = CustomDataSourceReference.TryCreateReference(
type,
out CustomDataSourceReference reference);
Assert.IsTrue(result);
Assert.IsNotNull(reference);
var metadata = type.GetCustomAttribute<CustomDataSourceAttribute>();
var dataSource = type.GetCustomAttribute<DataSourceAttribute>();
var tables = ((ICustomDataSource)Activator.CreateInstance(type)).DataTables;
Assert.IsNotNull(metadata);
Assert.IsNotNull(dataSource);
Assert.IsNotNull(tables);
Assert.AreEqual(type.Assembly.Location, reference.AssemblyPath);
Assert.AreEqual(metadata.Description, reference.Description);
Assert.AreEqual(metadata.Guid, reference.Guid);
Assert.AreEqual(metadata.Name, reference.Name);
Assert.AreEqual(type, reference.Type);
Assert.AreEqual(dataSource, reference.DataSource);
Assert.AreEqual(tables.Count(), reference.AvailableTables.Count());
Assert.IsTrue(tables.All(x => reference.AvailableTables.Contains(x)));
}
private static void RunCreateFailTest(Type type)
{
var result = CustomDataSourceReference.TryCreateReference(
type,
out CustomDataSourceReference reference);
Assert.IsFalse(result);
Assert.IsNull(reference);
}
[CustomDataSource("{8BAACFC9-CCBD-4856-A705-CA4C1CE28533}", "What", "Test")]
[FileDataSource("ext")]
protected class ProtectedType
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => throw new NotImplementedException();
public IEnumerable<TableDescriptor> MetadataTables { get; }
public IEnumerable<Option> CommandLineOptions => Enumerable.Empty<Option>();
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{29EF5347-53FD-49A0-8A03-C0262DE07BD4}", "What", "Test")]
[FileDataSource("ext")]
public class NestedPublicType
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables { get; }
public IEnumerable<Option> CommandLineOptions => Enumerable.Empty<Option>();
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{2D5E3373-88DA-4640-BD19-99FA8C437EB1}", "What", "Test")]
[FileDataSource("ext")]
public class SampleCds
: ICustomDataSource
{
private static readonly TableDescriptor[] tableDescriptors = new[]
{
Any.TableDescriptor(),
Any.TableDescriptor(),
};
public SampleCds()
{
this.DataTables = tableDescriptors;
}
public IEnumerable<TableDescriptor> DataTables { get; }
public IEnumerable<TableDescriptor> MetadataTables { get; }
public IEnumerable<Option> CommandLineOptions => Enumerable.Empty<Option>();
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Performance.SDK.Runtime.Discovery;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Discovery
{
internal class TestFindFiles
: AssemblyExtensionDiscovery.IFindFiles
{
public Func<string, string, SearchOption, IEnumerable<string>> enumerateFiles;
public IEnumerable<string> EnumerateFiles(string directoryPath, string searchPattern, SearchOption searchOption)
{
if (enumerateFiles == null)
{
return new List<string>();
}
return this.enumerateFiles.Invoke(directoryPath, searchPattern, searchOption);
}
}
internal class TestAssemblyLoader
: IAssemblyLoader
{
public bool SupportsIsolation { get; set; }
public Func<string, Assembly> loadAssembly;
public Assembly LoadAssembly(string assemblyPath)
{
return loadAssembly?.Invoke(assemblyPath);
}
}
internal class TestExtensionObserver
: IExtensionTypeObserver
{
public List<Type> ProcessTypes { get; } = new List<Type>();
public Action<Type, string> OnProcessType { get; set; }
public void ProcessType(Type type, string sourceName)
{
this.ProcessTypes.Add(type);
OnProcessType?.Invoke(type, sourceName);
}
internal bool Completed { get; set; }
public void DiscoveryComplete()
{
Assert.IsFalse(this.Completed);
this.Completed = true;
}
}
[TestClass]
public class AssemblyExtensionDiscoveryTests
{
private TestAssemblyLoader Loader { get; set; }
private AssemblyExtensionDiscovery Discovery { get; set; }
private TestFindFiles FindFiles { get; set; }
private List<TestExtensionObserver> Observers { get; set; }
private string TestAssemblyDirectory { get; set; }
private void RegisterObservers()
{
foreach (var observer in this.Observers)
{
this.Discovery.RegisterTypeConsumer(observer);
}
}
[TestInitialize]
public void Setup()
{
this.Loader = new TestAssemblyLoader();
this.Discovery = new AssemblyExtensionDiscovery(this.Loader);
this.FindFiles = new TestFindFiles();
this.Discovery.FindFiles = this.FindFiles;
this.Observers = new List<TestExtensionObserver>();
var testAssembly = this.GetType().Assembly;
this.TestAssemblyDirectory = Path.GetDirectoryName(testAssembly.GetCodeBaseAsLocalPath());
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_NullDirectoryPath()
{
Assert.ThrowsException<ArgumentNullException>(() => this.Discovery.ProcessAssemblies((string)null));
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_NullDirectoryPath2()
{
Assert.ThrowsException<ArgumentNullException>(
() => this.Discovery.ProcessAssemblies(
(string)null,
false,
null,
null,
false));
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_EmptyDirectoryPath()
{
Assert.ThrowsException<ArgumentException>(
() => this.Discovery.ProcessAssemblies(
string.Empty,
false,
null,
null,
false));
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_NoObservers()
{
this.FindFiles.enumerateFiles = (directory, searchPattern, searchOptions) =>
{
Assert.Fail("EnumerateFiles shouldn't be called if there are no observers.");
return new List<string>();
};
var testAssembly = this.GetType().Assembly;
var testAssemblyDirectory = Path.GetDirectoryName(testAssembly.GetCodeBaseAsLocalPath());
this.Discovery.ProcessAssemblies(testAssemblyDirectory);
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_SpecificSearchPattern()
{
var searchPatterns = new[] { "*.elephant", "*.bananas" };
var patternsSearched = new List<string>(searchPatterns.Length);
this.FindFiles.enumerateFiles = (directory, searchPattern, searchOptions) =>
{
Assert.IsTrue(
searchPattern == searchPatterns[0] || searchPattern == searchPatterns[1]);
patternsSearched.Add(searchPattern);
return new List<string>();
};
this.Observers.Add(new TestExtensionObserver());
RegisterObservers();
this.Discovery.ProcessAssemblies(this.TestAssemblyDirectory, false, searchPatterns, null, false);
Assert.AreEqual(searchPatterns.Length, patternsSearched.Count);
foreach (var pattern in searchPatterns)
{
Assert.IsTrue(patternsSearched.Contains(pattern));
}
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_SpecifyExclusions()
{
var exclusions = new[] { "Blawp.dll", };
this.FindFiles.enumerateFiles =
(directory, searchPattern, searchOptions) => new List<string>() {exclusions[0].ToLower(),};
this.Observers.Add(new TestExtensionObserver());
RegisterObservers();
this.Loader.loadAssembly =
s =>
{
Assert.Fail("LoadAssembly should not be called, the file should be excluded.");
return null;
};
this.Discovery.ProcessAssemblies(this.TestAssemblyDirectory, false, null, exclusions, false);
}
[TestMethod]
[UnitTest]
public void ProcessAssemblies_SpecifyCaseSensitiveExclusions()
{
var exclusions = new[] { "Blawp.dll", };
this.FindFiles.enumerateFiles =
(directory, searchPattern, searchOptions) => new List<string>() {exclusions[0].ToLower(),};
this.Observers.Add(new TestExtensionObserver());
RegisterObservers();
bool assemblyLoaded = false;
this.Loader.loadAssembly = s =>
{
assemblyLoaded = true;
return this.GetType().Assembly;
};
this.Discovery.ProcessAssemblies(this.TestAssemblyDirectory, false, null, exclusions, true);
Assert.IsTrue(assemblyLoaded);
}
}
}

Просмотреть файл

@ -0,0 +1,193 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Loader;
using Microsoft.Performance.SDK.Runtime.Discovery;
using Microsoft.Performance.SDK.Runtime.NetCoreApp.Discovery;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Discovery
{
[TestClass]
public class IsolatedAssemblyLoadingTests
{
private DirectoryInfo TestDir { get; set; }
private List<string> SharedAssemblyNames { get; set; }
private IsolationAssemblyLoader IsolationLoader { get; set; }
[TestInitialize]
public void Setup()
{
this.TestDir = Directory.CreateDirectory("TestDlls");
this.SharedAssemblyNames = new List<string>
{
"Microsoft.Performance.SDK.Runtime",
};
this.IsolationLoader = new IsolationAssemblyLoader();
}
[TestCleanup]
public void Cleanup()
{
try
{
IsolationAssemblyLoader.ClearLoadContexts();
}
catch (InvalidOperationException)
{
}
try
{
this.TestDir?.Delete(true);
}
catch (DirectoryNotFoundException)
{
}
catch (UnauthorizedAccessException)
{
}
}
[TestMethod]
[IntegrationTest]
public void SharedAssembliesLoadFromDefaultContext()
{
//
// Place a DLL we know we need to share in another folder.
//
var runtimeDll = typeof(AssemblyLoader).Assembly;
var runtimeDllName = runtimeDll.GetName().Name;
var runtimeDllFileName = Path.GetFileName(runtimeDll.GetCodeBaseAsLocalPath());
var pluginDllFilePath = Path.GetFullPath(Path.Combine(this.TestDir.Name, Path.GetFileName(runtimeDllFileName)));
if (!File.Exists(pluginDllFilePath))
{
File.Copy(runtimeDllFileName, pluginDllFilePath, true);
}
//
// And then load said folder.
//
var assembly = IsolationLoader.LoadAssembly(pluginDllFilePath);
//
// Making sure that the shared DLL did not load into the child context.
//
Assert.IsTrue(AssemblyLoadContext.Default.Assemblies.Any(x => x.GetName().Name == runtimeDllName));
Assert.AreEqual(
Path.GetFullPath(runtimeDllFileName),
AssemblyLoadContext.Default.Assemblies.Single(x => x.GetName().Name == runtimeDllName).GetCodeBaseAsLocalPath());
Assert.IsFalse(IsolationAssemblyLoader.LoadContexts.ContainsKey(this.TestDir.FullName));
}
[TestMethod]
[IntegrationTest]
public void UnsharedAssembliesLoadIntoGivenContext()
{
//
// Place a DLL into a plugin folder.
//
var notSharedDll = this.GetType().Assembly;
var notSharedDllName = notSharedDll.GetName().Name;
var notSharedDllFileName = Path.GetFileName(notSharedDll.GetCodeBaseAsLocalPath());
var notSharedDllFilePath = Path.GetFullPath(Path.Combine(this.TestDir.Name, notSharedDllFileName));
if (!File.Exists(notSharedDllFilePath))
{
File.Copy(notSharedDllFileName, notSharedDllFilePath, true);
}
//
// And load without anything shared.
//
var assembly = IsolationLoader.LoadAssembly(notSharedDllFilePath);
//
// Making sure that the DLL loaded into the specified context.
//
var loadedDllPath = assembly.GetCodeBaseAsLocalPath();
Assert.AreEqual(notSharedDllFilePath, loadedDllPath);
Assert.IsTrue(IsolationAssemblyLoader.LoadContexts.ContainsKey(this.TestDir.FullName));
var isolatedLoadContext = IsolationAssemblyLoader.LoadContexts[this.TestDir.FullName];
Assert.IsTrue(isolatedLoadContext.Assemblies.Any(x => x.GetName().Name == notSharedDllName));
}
[TestMethod]
[IntegrationTest]
public void SharedAndNotSharedAssembliesLoadIntoCorrectContexts()
{
//
// Place a DLL we know we need to share in another folder.
//
var sharedDll = typeof(AssemblyLoader).Assembly;
var sharedDllName = sharedDll.GetName().Name;
var sharedDllFileName = Path.GetFileName(sharedDll.GetCodeBaseAsLocalPath());
var sharedPluginDllFileName = Path.GetFullPath(Path.Combine(this.TestDir.Name, Path.GetFileName(sharedDllFileName)));
if (!File.Exists(sharedPluginDllFileName))
{
File.Copy(sharedDllFileName, sharedPluginDllFileName, true);
}
//
// Place a DLL that we are not sharing into the plugin folder.
//
var notSharedDll = this.GetType().Assembly;
var notSharedDllName = notSharedDll.GetName().Name;
var notSharedDllFileName = Path.GetFileName(notSharedDll.GetCodeBaseAsLocalPath());
var notSharedPluginDllFilePath = Path.GetFullPath(Path.Combine(this.TestDir.Name, Path.GetFileName(notSharedDllFileName)));
if (!File.Exists(notSharedPluginDllFilePath))
{
File.Copy(notSharedDllFileName, notSharedPluginDllFilePath, true);
}
//
// And then load said folder.
//
var sharedAssembly = this.IsolationLoader.LoadAssembly(sharedPluginDllFileName);
var notSharedAssembly = this.IsolationLoader.LoadAssembly(notSharedPluginDllFilePath);
//
// Making sure that the shared DLL did not load into the child context.
//
Assert.IsTrue(IsolationAssemblyLoader.LoadContexts.ContainsKey(this.TestDir.FullName));
var isolatedLoadContext = IsolationAssemblyLoader.LoadContexts[this.TestDir.FullName];
Assert.IsTrue(AssemblyLoadContext.Default.Assemblies.Any(x => x.GetName().Name == sharedDllName));
Assert.AreEqual(
Path.GetFullPath(sharedDllFileName),
AssemblyLoadContext.Default.Assemblies.Single(x => x.GetName().Name == sharedDllName).GetCodeBaseAsLocalPath());
Assert.IsFalse(isolatedLoadContext.Assemblies.Any(x => x.GetName().Name == sharedDllName));
//
// and Making sure that the shared DLL did not load into the child context.
//
Assert.IsTrue(isolatedLoadContext.Assemblies.Any(x => x.GetName().Name == notSharedDllName));
Assert.AreEqual(
notSharedPluginDllFilePath,
isolatedLoadContext.Assemblies.Single(x => x.GetName().Name == notSharedDllName).GetCodeBaseAsLocalPath());
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Discovery;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime.Tests.Discovery
{
[TestClass]
public class ReflectionPlugInCatalogTests
{
private FakeReferenceFactory ReferenceFactory { get; set; }
private ReflectionPlugInCatalog Sut { get; set; }
private TestExtensionProvider ExtensionProvider { get; set; }
[TestInitialize]
public void Initialize()
{
this.ReferenceFactory = new FakeReferenceFactory();
this.ExtensionProvider = new TestExtensionProvider();
this.Sut = new ReflectionPlugInCatalog(
this.ExtensionProvider,
this.ReferenceFactory.TryCreateCustomDataSourceReference);
}
[TestMethod]
[UnitTest]
public void AddInsEmptyOnConstruction()
{
Assert.IsFalse(this.Sut.PlugIns.Any());
}
[TestMethod]
[UnitTest]
public void SkipsInvalidType()
{
var testType = typeof(CdsWithoutCdsAttribute);
this.Sut.ProcessType(testType, testType.FullName);
Assert.IsFalse(this.Sut.PlugIns.Any());
}
[TestMethod]
[UnitTest]
public void AddsValidTypes()
{
var testType1 = typeof(CdsOne);
var testType1Guid = testType1.GetCustomAttribute<CustomDataSourceAttribute>().Guid;
var testType2 = typeof(CdsTwo);
var testType2Guid = testType2.GetCustomAttribute<CustomDataSourceAttribute>().Guid;
this.ReferenceFactory.SetupReference(testType1);
this.ReferenceFactory.SetupReference(testType2);
this.Sut.ProcessType(testType1, testType1.FullName);
Assert.AreEqual(1, this.Sut.PlugIns.Count());
this.Sut.ProcessType(testType2, testType2.FullName);
Assert.AreEqual(2, this.Sut.PlugIns.Count());
Assert.IsTrue(this.Sut.PlugIns.Any(cds => cds.Guid == testType1Guid));
Assert.IsTrue(this.Sut.PlugIns.Any(cds => cds.Guid == testType2Guid));
}
private sealed class FakeReferenceFactory
{
public FakeReferenceFactory()
{
this.TypeToReference = new Dictionary<Type, CustomDataSourceReference>();
}
public IDictionary<Type, CustomDataSourceReference> TypeToReference { get; }
public bool TryCreateCustomDataSourceReference(
Type type,
out CustomDataSourceReference reference)
{
return this.TypeToReference.TryGetValue(type, out reference);
}
public void SetupReference(Type type)
{
if (type != null)
{
if (CustomDataSourceReference.TryCreateReference(
type,
out CustomDataSourceReference reference))
{
this.TypeToReference[type] = reference;
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All Rights Reserved.
using Microsoft.Performance.SDK.Runtime.Discovery;
namespace Microsoft.Performance.SDK.Runtime.Tests.Discovery
{
public class TestExtensionProvider
: IExtensionTypeProvider
{
public void RegisterTypeConsumer(IExtensionTypeObserver observer)
{
this.Observer = observer;
}
public IExtensionTypeObserver Observer { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,212 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class CustomDataProcessorExtensibilitySupportTests
{
[Table]
public class TestTable1
{
public static readonly string SourceParserId = "SourceId1";
public static TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{C2AC873F-B88E-46A1-A08B-0CDF1D0C9F18}"),
"Test Table with Requirements",
"This table has required data extensions",
"Other",
true,
requiredDataCookers: new List<DataCookerPath> { new DataCookerPath(SourceParserId, "CookerId1") })
{ Type = typeof(TestTable1) };
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval dataRetrieval)
{
}
}
[Table]
public class TestTable2
{
public static readonly string SourceParserId = "SourceId2";
public static TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{BCF87F9D-E7D9-438D-8799-179AC39DF3FD}"),
"Test Table with Requirements",
"This table has required data extensions",
"Other",
true,
requiredDataCookers: new List<DataCookerPath> { new DataCookerPath(SourceParserId, "CookerId1") })
{ Type = typeof(TestTable2) };
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval dataRetrieval)
{
}
}
[UnitTest]
[TestMethod]
public void AddTableWithNoRequiredDataExtensions()
{
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor();
var tableDescriptorWithNoRequirements = new TableDescriptor(
Guid.Parse("{94267E78-5B18-4FA7-A6C0-8E21B06DF65A}"),
"Simple Test Table",
"This table has no required data extensions");
Assert.IsFalse(cdp.ExtensibilitySupport.AddTable(tableDescriptorWithNoRequirements));
}
[UnitTest]
[TestMethod]
public void AddTableWithRequiredDataExtensions()
{
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor();
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
}
[UnitTest]
[TestMethod]
public void AddTableWithMissingRequirements()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
// Not adding the source data cooker that is required by the table, so we expect that FinalizeTables will
// remove the table.
//
cdp.ExtensibilitySupport.FinalizeTables();
var requiredCookers = cdp.ExtensibilitySupport.GetAllRequiredSourceDataCookers();
Assert.AreEqual(0, requiredCookers.Count);
var dataRetrieval = cdp.ExtensibilitySupport.GetDataExtensionRetrieval(TestTable2.TableDescriptor);
Assert.IsNull(dataRetrieval);
}
[UnitTest]
[TestMethod]
public void FinalizeTablesWithSameSourceParser()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
var sourceCooker = new TestSourceDataCooker() { Path = new DataCookerPath(TestTable1.SourceParserId, "CookerId1") };
var sourceCookerReference = new TestSourceDataCookerReference(false)
{ availability = DataExtensionAvailability.Available, Path = sourceCooker.Path };
cdp.ExtensionRepository.sourceCookersByPath.Add(sourceCooker.Path, sourceCookerReference);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
cdp.ExtensibilitySupport.FinalizeTables();
var requiredCookers = cdp.ExtensibilitySupport.GetAllRequiredSourceDataCookers();
Assert.AreEqual(1, requiredCookers.Count);
Assert.AreEqual(sourceCooker.Path, requiredCookers.First());
var dataRetrieval = cdp.ExtensibilitySupport.GetDataExtensionRetrieval(TestTable1.TableDescriptor);
Assert.IsNotNull(dataRetrieval);
}
[UnitTest]
[TestMethod]
public void FinalizeTablesFromVariedParsers()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
var sourceCooker = new TestSourceDataCooker() { Path = new DataCookerPath(TestTable2.SourceParserId, "CookerId1") };
var sourceCookerReference = new TestSourceDataCookerReference(false)
{ availability = DataExtensionAvailability.Available, Path = sourceCooker.Path };
cdp.ExtensionRepository.sourceCookersByPath.Add(sourceCooker.Path, sourceCookerReference);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable2.TableDescriptor));
cdp.ExtensibilitySupport.FinalizeTables();
var requiredCookers = cdp.ExtensibilitySupport.GetAllRequiredSourceDataCookers();
Assert.AreEqual(0, requiredCookers.Count);
var dataRetrieval = cdp.ExtensibilitySupport.GetDataExtensionRetrieval(TestTable2.TableDescriptor);
Assert.IsNull(dataRetrieval);
}
[UnitTest]
[TestMethod]
public void FinalizeTwiceThrows()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
var sourceCooker = new TestSourceDataCooker() { Path = new DataCookerPath(TestTable1.SourceParserId, "CookerId1") };
var sourceCookerReference = new TestSourceDataCookerReference(false)
{ availability = DataExtensionAvailability.Available, Path = sourceCooker.Path };
cdp.ExtensionRepository.sourceCookersByPath.Add(sourceCooker.Path, sourceCookerReference);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
cdp.ExtensibilitySupport.FinalizeTables();
Assert.ThrowsException<InvalidOperationException>(() => cdp.ExtensibilitySupport.FinalizeTables());
}
[UnitTest]
[TestMethod]
public void GetAllRequiredSourceDataCookersWithoutFinalizeThrows()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
var sourceCooker = new TestSourceDataCooker() { Path = new DataCookerPath(TestTable1.SourceParserId, "CookerId1") };
var sourceCookerReference = new TestSourceDataCookerReference(false)
{ availability = DataExtensionAvailability.Available, Path = sourceCooker.Path };
cdp.ExtensionRepository.sourceCookersByPath.Add(sourceCooker.Path, sourceCookerReference);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
Assert.ThrowsException<InvalidOperationException>(() => cdp.ExtensibilitySupport.GetAllRequiredSourceDataCookers());
}
[UnitTest]
[TestMethod]
public void GetDataExtensionRetrievalWithoutFinalizeThrows()
{
// Just make sure that this doesn't throw an exception
var cdp = TestCustomDataProcessor.CreateTestCustomDataProcessor(TestTable1.SourceParserId);
var sourceCooker = new TestSourceDataCooker() { Path = new DataCookerPath(TestTable1.SourceParserId, "CookerId1") };
var sourceCookerReference = new TestSourceDataCookerReference(false)
{ availability = DataExtensionAvailability.Available, Path = sourceCooker.Path };
cdp.ExtensionRepository.sourceCookersByPath.Add(sourceCooker.Path, sourceCookerReference);
Assert.IsTrue(cdp.ExtensibilitySupport.AddTable(TestTable1.TableDescriptor));
Assert.ThrowsException<InvalidOperationException>(() => cdp.ExtensibilitySupport.GetDataExtensionRetrieval(TestTable1.TableDescriptor));
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataCookers
{
public class ValidSourceDataCooker
: BaseSourceDataCooker<TestDataElement, TestDataContext, int>
{
public ValidSourceDataCooker()
: base("SourceId", "CookerId")
{
}
public override string Description { get; } = "Test Source Data Cooker";
public override ReadOnlyHashSet<int> DataKeys { get; } = new ReadOnlyHashSet<int>(new HashSet<int>());
public override DataProcessingResult CookDataElement(
TestDataElement data,
TestDataContext context,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,431 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Dependency;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class DataExtensionDependencyTests
{
[TestMethod]
[UnitTest]
public void ConstructorThrowsForNullDependencyTarget()
{
Assert.ThrowsException<ArgumentNullException>(
() => new DataExtensionDependencyState((IDataExtensionDependencyTarget)null));
}
[TestMethod]
[UnitTest]
public void ConstructorThrowsForNullOther()
{
Assert.ThrowsException<ArgumentNullException>(
() => new DataExtensionDependencyState((DataExtensionDependencyState)null));
}
[TestMethod]
[UnitTest]
public void AddError()
{
string testMessage = "Test Error Message";
var target = new TestDataExtensionDependencyTarget();
var targetDependency = new DataExtensionDependencyState(target);
targetDependency.AddError(testMessage);
Assert.AreEqual(targetDependency.Errors.Count, 1);
Assert.IsTrue(targetDependency.Errors.First() == testMessage);
}
[TestMethod]
[UnitTest]
public void UpdateAvailability()
{
var target = new TestDataExtensionDependencyTarget();
var targetDependency = new DataExtensionDependencyState(target);
// Unavailable -> Available
targetDependency.UpdateAvailability(DataExtensionAvailability.Available);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.Available);
// Available -> Error
targetDependency.UpdateAvailability(DataExtensionAvailability.Error);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.Error);
// Error X->X Available
targetDependency.UpdateAvailability(DataExtensionAvailability.Available);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.Error);
// Error -> Undetermined
targetDependency.AddError("Should go away");
targetDependency.UpdateAvailability(DataExtensionAvailability.Undetermined);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.Undetermined);
Assert.AreEqual(targetDependency.Errors.Count, 0);
// Undetermined -> MissingIndirectRequirement
targetDependency.UpdateAvailability(DataExtensionAvailability.MissingIndirectRequirement);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.MissingIndirectRequirement);
// MissingIndirectRequirement -> MissingRequirement
targetDependency.UpdateAvailability(DataExtensionAvailability.MissingRequirement);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.MissingRequirement);
// MissingRequirement X->X MissingIndirectRequirement
targetDependency.UpdateAvailability(DataExtensionAvailability.MissingIndirectRequirement);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.MissingRequirement);
// MissingRequirement X->X IndirectError
targetDependency.UpdateAvailability(DataExtensionAvailability.IndirectError);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.IndirectError);
// IndirectError -> Error
targetDependency.UpdateAvailability(DataExtensionAvailability.Error);
Assert.AreEqual(targetDependency.Availability, DataExtensionAvailability.Error);
}
[TestMethod]
[UnitTest]
public void EstablishAvailability_NoDependencies()
{
var testRepo = new TestDataExtensionRepository();
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 0);
}
/// <summary>
/// This test case adds one required source data cooker to the extension.
/// The required source cooker has already been marked as available, so it is expected
/// that the extension will also be marked as available.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_OneAvailableDataCooker()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
var cooker1 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker1"),
};
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.sourceCookersByPath.Add(cooker1.Path, cooker1);
cooker0.requiredDataCookers.Add(new DataCookerPath(cooker1.Path));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 1);
}
/// <summary>
/// This test case adds one required source data cooker to the extension.
/// The required source cooker has been marked as error, so it is expected
/// that the extension will also be marked with an indirect error.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_OneErroredDataCooker()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
var cooker1 = new TestSourceDataCookerReference(false)
{
Path = new DataCookerPath("TestSourceParser", "Cooker1"),
availability = DataExtensionAvailability.Error,
};
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.sourceCookersByPath.Add(cooker1.Path, cooker1);
cooker0.requiredDataCookers.Add(new DataCookerPath(cooker1.Path));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.IndirectError);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 1);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 1);
}
/// <summary>
/// This test case adds one required source data cooker (level 1) to the extension, and that
/// data cooker has a requirement on a different data cooker (level 2).
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_RequiresCookers_TwoLevelsDeep()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
var cooker1 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker1"),
};
var cooker2 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker2"),
};
// the repository has all source cookers
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.sourceCookersByPath.Add(cooker1.Path, cooker1);
testRepo.sourceCookersByPath.Add(cooker2.Path, cooker2);
// setup the requirements for the cookers
cooker0.requiredDataCookers.Add(cooker1.Path);
cooker1.requiredDataCookers.Add(cooker2.Path);
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker2.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker2.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker2.DependencyReferences.RequiredSourceDataCookerPaths.Count, 0);
Assert.IsTrue(cooker1.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker1.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker1.DependencyReferences.RequiredSourceDataCookerPaths.Count, 1);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 2);
}
/// <summary>
/// Source data cooker, Cooker0, depends on a source data cooker that is not available.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_MissingDependency()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
// the repository has all source cookers
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
// setup the requirements for the cookers
cooker0.requiredDataCookers.Add(new DataCookerPath("TestSourceParser", "Cooker1"));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.MissingRequirement);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyState.MissingDataCookers.Count, 1);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 0);
}
/// <summary>
/// This test case adds one required source data cooker (Cooker1) to a source cooker (Cooker0).
/// Cooker1 has a requirement on a different data cooker (Cooker2).
/// Cooker2 has a dependency back to Cooker0, which should be caught and an error generated.
/// Cooker0 should have an error for the circular dependency. The indirect error from Cooker1
/// won't be stored as IndirectError is a lower priority than Error.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_ThrowsOnCircularReferences()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
var cooker1 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker1"),
};
var cooker2 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker2"),
};
// the repository has all source cookers
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.sourceCookersByPath.Add(cooker1.Path, cooker1);
testRepo.sourceCookersByPath.Add(cooker2.Path, cooker2);
// setup the requirements for the cookers
cooker0.requiredDataCookers.Add(cooker1.Path);
cooker1.requiredDataCookers.Add(cooker2.Path);
cooker2.requiredDataCookers.Add(cooker0.Path);
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Error);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 1);
}
/// <summary>
/// This test case adds one required source data cooker to the extension.
/// Because the required cooker has a different source id than the base,
/// we expect the additional validation method to result in an error.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_PerformAdditionalValidationIsCalled()
{
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser0", "Cooker0"),
};
var cooker1 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSourceParser1", "Cooker1"),
};
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.sourceCookersByPath.Add(cooker1.Path, cooker1);
cooker0.requiredDataCookers.Add(new DataCookerPath(cooker1.Path));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Error);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 1);
}
/// <summary>
/// This test case adds one required composite data cooker to the extension.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_AddOneCompositeDataCooker()
{
var cooker0 = new TestCompositeDataCookerReference
{
Path = new DataCookerPath("TestSourceParser", "Cooker0"),
};
var cooker1 = new TestCompositeDataCookerReference
{
Path = new DataCookerPath(DataCookerPath.EmptySourceParserId, "Cooker1"),
};
var testRepo = new TestDataExtensionRepository();
testRepo.compositeCookersByPath.Add(cooker0.Path, cooker0);
testRepo.compositeCookersByPath.Add(cooker1.Path, cooker1);
cooker0.requiredDataCookers.Add(new DataCookerPath(cooker1.Path));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker1.Availability == DataExtensionAvailability.Available);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredCompositeDataCookerPaths.Count, 1);
}
/// <summary>
/// A composite data cooker requires a composite data cooker that isn't available.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_MissingCompositeDataCooker()
{
var cooker0 = new TestCompositeDataCookerReference
{
Path = new DataCookerPath(DataCookerPath.EmptySourceParserId, "Cooker0"),
};
var testRepo = new TestDataExtensionRepository();
testRepo.compositeCookersByPath.Add(cooker0.Path, cooker0);
// this doesn't exist
cooker0.requiredDataCookers.Add(new DataCookerPath("Cooker1"));
cooker0.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.MissingRequirement);
Assert.AreEqual(cooker0.DependencyState.Errors.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredSourceDataCookerPaths.Count, 0);
Assert.AreEqual(cooker0.DependencyReferences.RequiredCompositeDataCookerPaths.Count, 0);
Assert.AreEqual(cooker0.DependencyState.MissingDataCookers.Count, 1);
}
/// <summary>
/// A data processor requires a source data cooker and a composite data cooker,
/// both which are available.
/// </summary>
[TestMethod]
[UnitTest]
public void EstablishAvailability_DataProcessorReliesOnAvailableDataCookers()
{
var dataProcessor = new TestDataProcessorReference
{
Id = "DataProcessor",
};
var cooker0 = new TestSourceDataCookerReference
{
Path = new DataCookerPath("TestSource0", "Cooker0"),
};
var cooker1 = new TestCompositeDataCookerReference
{
Path = new DataCookerPath(DataCookerPath.EmptySourceParserId, "Cooker0"),
};
var testRepo = new TestDataExtensionRepository();
testRepo.sourceCookersByPath.Add(cooker0.Path, cooker0);
testRepo.compositeCookersByPath.Add(cooker1.Path, cooker1);
// setup the requirements
dataProcessor.requiredDataCookers.Add(cooker0.Path);
dataProcessor.requiredDataCookers.Add(cooker1.Path);
dataProcessor.ProcessDependencies(testRepo);
Assert.IsTrue(cooker0.Availability == DataExtensionAvailability.Available);
Assert.IsTrue(cooker1.Availability == DataExtensionAvailability.Available);
Assert.IsTrue(dataProcessor.Availability == DataExtensionAvailability.Available);
Assert.AreEqual(dataProcessor.DependencyState.Errors.Count, 0);
Assert.AreEqual(dataProcessor.DependencyReferences.RequiredSourceDataCookerPaths.Count, 1);
Assert.AreEqual(dataProcessor.DependencyReferences.RequiredCompositeDataCookerPaths.Count, 1);
Assert.AreEqual(dataProcessor.DependencyState.MissingDataCookers.Count, 0);
}
}
}

Просмотреть файл

@ -0,0 +1,199 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class DataExtensionRetrievalFactoryTests
{
[TestMethod]
[UnitTest]
public void Constructor()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
Assert.AreEqual(cookedDataRetrieval, dataExtensionRetrievalFactory.CookedSourceData);
Assert.AreEqual(dataExtensionRepository, dataExtensionRetrievalFactory.DataExtensionRepository);
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForCompositeDataCookerSucceeds()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var sourceDataCookerReference1 = new TestSourceDataCookerReference(false)
{
availability = DataExtensionAvailability.Available,
Path = new DataCookerPath("Source1", "SourceCooker1"),
};
var dataCookerReference = new TestCompositeDataCookerReference(false)
{
availability = DataExtensionAvailability.Available,
Path = new DataCookerPath("CompositeCooker1"),
};
dataCookerReference.requiredDataCookers.Add(sourceDataCookerReference1.Path);
dataExtensionRepository.sourceCookersByPath.Add(sourceDataCookerReference1.Path, sourceDataCookerReference1);
dataExtensionRepository.compositeCookersByPath.Add(dataCookerReference.Path, dataCookerReference);
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
var dataRetrieval = dataExtensionRetrievalFactory.CreateDataRetrievalForCompositeDataCooker(dataCookerReference.Path);
Assert.IsNotNull(dataRetrieval);
// check that the cache is working as expected
var dataRetrieval2 = dataExtensionRetrievalFactory.CreateDataRetrievalForCompositeDataCooker(dataCookerReference.Path);
Assert.IsNotNull(dataRetrieval2);
Assert.IsTrue(object.ReferenceEquals(dataRetrieval, dataRetrieval2));
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForCompositeDataCooker_MissingCookerThrows()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var cookerPath = new DataCookerPath("CompositeCooker1");
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
Assert.ThrowsException<ArgumentException>(() =>
dataExtensionRetrievalFactory.CreateDataRetrievalForCompositeDataCooker(cookerPath));
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForCompositeDataCooker_NotAvailableCookerThrows()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var dataCookerReference = new TestCompositeDataCookerReference(false)
{
availability = DataExtensionAvailability.Error,
Path = new DataCookerPath("CompositeCooker1"),
};
dataExtensionRepository.compositeCookersByPath.Add(dataCookerReference.Path, dataCookerReference);
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
Assert.ThrowsException<ArgumentException>(() =>
dataExtensionRetrievalFactory.CreateDataRetrievalForCompositeDataCooker(dataCookerReference.Path));
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForTableSucceeds()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var sourceDataCookerReference1 = new TestSourceDataCookerReference(false)
{
availability = DataExtensionAvailability.Available,
Path = new DataCookerPath("Source1", "SourceCooker1"),
};
var dataCookerReference = new TestCompositeDataCookerReference(false)
{
availability = DataExtensionAvailability.Available,
Path = new DataCookerPath("CompositeCooker1"),
};
dataCookerReference.requiredDataCookers.Add(sourceDataCookerReference1.Path);
var tableReference = new TestTableExtensionReference(false)
{
availability = DataExtensionAvailability.Available,
TableDescriptor = new TableDescriptor(
Guid.Parse("{F0F20004-E159-447B-B122-BD820C2A9908}"), "Test Table", "Test Table"),
};
dataExtensionRepository.sourceCookersByPath.Add(sourceDataCookerReference1.Path, sourceDataCookerReference1);
dataExtensionRepository.compositeCookersByPath.Add(dataCookerReference.Path, dataCookerReference);
dataExtensionRepository.tablesById.Add(tableReference.TableDescriptor.Guid, tableReference);
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
var dataRetrieval = dataExtensionRetrievalFactory.CreateDataRetrievalForTable(tableReference.TableDescriptor.Guid);
Assert.IsNotNull(dataRetrieval);
// check that the cache is working as expected
var dataRetrieval2 = dataExtensionRetrievalFactory.CreateDataRetrievalForTable(tableReference.TableDescriptor.Guid);
Assert.IsNotNull(dataRetrieval2);
Assert.IsTrue(object.ReferenceEquals(dataRetrieval, dataRetrieval2));
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForTable_MissingCookerThrows()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var tableId = Guid.Parse("{6BB197C8-B3BE-4926-B23A-6F01451D80A3}");
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
Assert.ThrowsException<ArgumentException>(() =>
dataExtensionRetrievalFactory.CreateDataRetrievalForTable(tableId));
}
[TestMethod]
[UnitTest]
public void CreateDataRetrievalForTable_NotAvailableCookerThrows()
{
var cookedDataRetrieval = new TestCookedDataRetrieval();
var dataExtensionRepository = new TestDataExtensionRepository();
var dataCookerReference = new TestCompositeDataCookerReference(false)
{
availability = DataExtensionAvailability.Error,
Path = new DataCookerPath("CompositeCooker1"),
};
var tableReference = new TestTableExtensionReference(false)
{
availability = DataExtensionAvailability.IndirectError,
TableDescriptor = new TableDescriptor(
Guid.Parse("{F0F20004-E159-447B-B122-BD820C2A9908}"), "Test Table", "Test Table"),
};
dataExtensionRepository.compositeCookersByPath.Add(dataCookerReference.Path, dataCookerReference);
dataExtensionRepository.tablesById.Add(tableReference.TableDescriptor.Guid, tableReference);
var dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(cookedDataRetrieval, dataExtensionRepository);
Assert.ThrowsException<ArgumentException>(() =>
dataExtensionRetrievalFactory.CreateDataRetrievalForTable(tableReference.TableDescriptor.Guid));
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes
{
public struct TestDataContext
{
public int DataElementIndex { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes
{
public class TestDataElement
: IKeyedDataType<int>
{
public string Name { get; set; }
public int Id { get; set; }
public byte[] Data { get; set; }
public int CompareTo(int other)
{
return Id.CompareTo(other);
}
public int GetKey()
{
return Id;
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes
{
public class TestParserContext
{
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes
{
public class TestRecord
: IKeyedDataType<int>
{
public int Key { get; set; }
public string Value { get; set; }
public int CompareTo(int otherKey)
{
return this.Key.CompareTo(otherKey);
}
public int GetKey()
{
return this.Key;
}
}
}

Просмотреть файл

@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataCookers;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
internal class InternalSourceDataCooker
: BaseSourceDataCooker<TestDataElement, TestDataContext, int>
{
public InternalSourceDataCooker()
: base(new DataCookerPath("SourceId", "CookerId"))
{
}
public override string Description { get; } = "Test Source Data Cooker";
public override ReadOnlyHashSet<int> DataKeys { get; } = new ReadOnlyHashSet<int>(new HashSet<int>());
public override DataProcessingResult CookDataElement(
TestDataElement data,
TestDataContext context,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
public class NoEmptyPublicConstructorSourceDataCooker
: BaseSourceDataCooker<TestDataElement, TestDataContext, int>
{
public NoEmptyPublicConstructorSourceDataCooker(string sourceId, string cookerId)
: base(sourceId, cookerId)
{
}
public override string Description { get; } = "Test Source Data Cooker";
public override ReadOnlyHashSet<int> DataKeys { get; } = new ReadOnlyHashSet<int>(new HashSet<int>());
public override DataProcessingResult CookDataElement(
TestDataElement data,
TestDataContext context,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
public class NoPublicConstructorSourceDataCooker
: BaseSourceDataCooker<TestDataElement, TestDataContext, int>
{
internal NoPublicConstructorSourceDataCooker()
: base("SourceId", "CookerId")
{
}
public override string Description { get; } = "Test Source Data Cooker";
public override ReadOnlyHashSet<int> DataKeys { get; } = new ReadOnlyHashSet<int>(new HashSet<int>());
public override DataProcessingResult CookDataElement(
TestDataElement data,
TestDataContext context,
CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
[TestClass]
public class SourceDataCookerReferenceTests
{
[TestMethod]
[UnitTest]
public void TryCreateReferenceSucceeds()
{
var result =
SourceDataCookerReference.TryCreateReference(
typeof(ValidSourceDataCooker),
out var sourceDataCookerReference);
Assert.IsTrue(result);
Assert.IsNotNull(sourceDataCookerReference);
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceFails1()
{
var result =
SourceDataCookerReference.TryCreateReference(
typeof(InternalSourceDataCooker),
out var sourceDataCookerReference);
Assert.IsFalse(result);
Assert.IsNull(sourceDataCookerReference);
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceFails2()
{
var result =
SourceDataCookerReference.TryCreateReference(
typeof(NoEmptyPublicConstructorSourceDataCooker),
out var sourceDataCookerReference);
Assert.IsFalse(result);
Assert.IsNull(sourceDataCookerReference);
}
[TestMethod]
[UnitTest]
public void TryCreateReferenceFails3()
{
var result =
SourceDataCookerReference.TryCreateReference(
typeof(NoPublicConstructorSourceDataCooker),
out var sourceDataCookerReference);
Assert.IsFalse(result);
Assert.IsNull(sourceDataCookerReference);
}
}
}

Просмотреть файл

@ -0,0 +1,291 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Scheduling;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class SourceDataCookerSchedulerTests
{
public static readonly string SourceParserId = "TestSourceParser";
private static readonly ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType> EmptyDependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>());
private readonly List<IDataCookerDescriptor> expectedPass0Cookers = new List<IDataCookerDescriptor>();
private readonly List<IDataCookerDescriptor> expectedPass1Cookers = new List<IDataCookerDescriptor>();
private ValidSourceDataCookerWithoutDependencies dataCooker1;
private ValidSourceDataCookerWithoutDependencies dataCooker2;
private ValidSourceDataCookerWithoutDependencies dataCooker3;
private ValidSourceDataCookerWithoutDependencies dataCooker4;
private ValidSourceDataCookerWithoutDependencies dataCooker5;
private ValidSourceDataCookerWithoutDependencies dataCooker6;
private ValidSourceDataCookerWithoutDependencies dataCooker7;
[TestInitialize]
public void TestSetup()
{
// Pass: 0, Block: 0
this.dataCooker1 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker1"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[] { }
};
// Pass: 0, Block: 0
this.dataCooker4 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker4"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>()
{
{ this.dataCooker1.Path, DataCookerDependencyType.SamePass },
}),
RequiredDataCookers = new[] { this.dataCooker1.Path }
};
// Pass: 0, Block: 1
this.dataCooker3 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker3"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.AsConsumed,
DependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>()
{
{ this.dataCooker1.Path, DataCookerDependencyType.AsConsumed },
}),
RequiredDataCookers = new[] { this.dataCooker1.Path }
};
// Pass: 1, Block: 0
this.dataCooker2 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker2"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.AsConsumed,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new[] { this.dataCooker1.Path }
};
// Pass: 1, Block: 1
this.dataCooker5 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker5"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new[] { this.dataCooker2.Path }
};
// Pass: 1, Block: 2
this.dataCooker6 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker6"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>()
{
{ this.dataCooker5.Path, DataCookerDependencyType.AsConsumed },
}),
RequiredDataCookers = new[] { this.dataCooker5.Path }
};
// Pass: 1, Block: 2
this.dataCooker7 = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "Cooker7"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>()
{
{ this.dataCooker6.Path, DataCookerDependencyType.SamePass },
}),
RequiredDataCookers = new[] { this.dataCooker6.Path }
};
this.expectedPass0Cookers.Add(this.dataCooker1);
this.expectedPass0Cookers.Add(this.dataCooker3);
this.expectedPass0Cookers.Add(this.dataCooker4);
this.expectedPass1Cookers.Add(this.dataCooker2);
this.expectedPass1Cookers.Add(this.dataCooker5);
this.expectedPass1Cookers.Add(this.dataCooker6);
this.expectedPass1Cookers.Add(this.dataCooker7);
}
[TestMethod]
[UnitTest]
public void ConstructorTest()
{
var scheduler = new SourceDataCookerScheduler(SourceParserId);
Assert.IsTrue(StringComparer.Ordinal.Equals(SourceParserId, scheduler.SourceParserId));
}
/// <summary>
/// This is a general test where order of data cookers passed into
/// <see cref="SourceDataCookerScheduler.ScheduleDataCookers"/> is not ordered specifically in a one direction.
/// </summary>
[TestMethod]
[UnitTest]
public void ScheduleDataCookers()
{
var testDataCookers = new List<ValidSourceDataCookerWithoutDependencies>
{
// Note: these are out-of-pass/block-order on purpose, as I didn't want them to be added in-order.
this.dataCooker1,
this.dataCooker2,
this.dataCooker3,
this.dataCooker4,
this.dataCooker5,
this.dataCooker6,
this.dataCooker7
};
var scheduler = new SourceDataCookerScheduler(SourceParserId);
scheduler.ScheduleDataCookers(testDataCookers);
var cookersByPass = scheduler.DataCookersBySourcePass;
// Expect that there are 2 passes
Assert.AreEqual(cookersByPass.Count, 2);
// Check the sizes of the 2 passes
Assert.AreEqual(cookersByPass[0].Count, this.expectedPass0Cookers.Count);
Assert.AreEqual(cookersByPass[1].Count, this.expectedPass1Cookers.Count);
// Check the cookers are in their expected passes
Assert.IsTrue(cookersByPass[0].Contains(this.dataCooker1));
Assert.IsTrue(cookersByPass[0].Contains(this.dataCooker3));
Assert.IsTrue(cookersByPass[0].Contains(this.dataCooker4));
Assert.IsTrue(cookersByPass[1].Contains(this.dataCooker2));
Assert.IsTrue(cookersByPass[1].Contains(this.dataCooker5));
Assert.IsTrue(cookersByPass[1].Contains(this.dataCooker6));
Assert.IsTrue(cookersByPass[1].Contains(this.dataCooker7));
// Verify that necessary ordering withing a pass is met
Assert.IsTrue(cookersByPass[0].IndexOf(this.dataCooker1) < cookersByPass[0].IndexOf(this.dataCooker3));
Assert.IsTrue(cookersByPass[0].IndexOf(this.dataCooker4) < cookersByPass[0].IndexOf(this.dataCooker3));
Assert.IsTrue(cookersByPass[1].IndexOf(this.dataCooker2) < cookersByPass[1].IndexOf(this.dataCooker5));
Assert.IsTrue(cookersByPass[1].IndexOf(this.dataCooker2) < cookersByPass[1].IndexOf(this.dataCooker6));
Assert.IsTrue(cookersByPass[1].IndexOf(this.dataCooker2) < cookersByPass[1].IndexOf(this.dataCooker7));
Assert.IsTrue(cookersByPass[1].IndexOf(this.dataCooker5) < cookersByPass[1].IndexOf(this.dataCooker6));
Assert.IsTrue(cookersByPass[1].IndexOf(this.dataCooker5) < cookersByPass[1].IndexOf(this.dataCooker7));
}
/// <summary>
/// This is meant to test the case where a data cooker that requires other data cookers is passed into
/// <see cref="SourceDataCookerScheduler.ScheduleDataCookers"/> before the required data cookers.
/// </summary>
[TestMethod]
[UnitTest]
public void ScheduleDependenciesAfterDependent()
{
var dataCookerA = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "CookerA"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[] { }
};
var dataCookerB = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "CookerB"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[] { }
};
var dataCookerC = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "CookerC"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[] { }
};
var dataCookerD = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "CookerD"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.AsConsumed,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[]
{
dataCookerA.Path,
dataCookerB.Path,
dataCookerC.Path
}
};
var dataCookerE = new ValidSourceDataCookerWithoutDependencies
{
Path = new DataCookerPath(SourceParserId, "CookerE"),
Description = "Test data cooker",
DataProductionStrategy = DataProductionStrategy.PostSourceParsing,
DependencyTypes = EmptyDependencyTypes,
RequiredDataCookers = new DataCookerPath[]
{
dataCookerD.Path
}
};
var sourceCookers = new List<ValidSourceDataCookerWithoutDependencies>()
{
dataCookerE,
dataCookerD,
dataCookerA,
dataCookerB,
dataCookerC
};
var scheduler = new SourceDataCookerScheduler(SourceParserId);
scheduler.ScheduleDataCookers(sourceCookers);
List<List<IDataCookerDescriptor>> cookersByPass = scheduler.DataCookersBySourcePass;
// Expect that there are 2 passes
Assert.AreEqual(cookersByPass.Count, 2);
// Check the sizes of the 2 passes
// Cookers A, B, C have no dependencies and should be in pass 0.
// Cookers D & E have dependencies and should be in pass 1.
Assert.AreEqual(cookersByPass[0].Count, 3);
Assert.AreEqual(cookersByPass[1].Count, 2);
// Check the cookers are in their expected passes
Assert.IsTrue(cookersByPass[0].Contains(dataCookerA));
Assert.IsTrue(cookersByPass[0].Contains(dataCookerB));
Assert.IsTrue(cookersByPass[0].Contains(dataCookerC));
Assert.IsTrue(cookersByPass[1].Contains(dataCookerD));
Assert.IsTrue(cookersByPass[1].Contains(dataCookerE));
// Verify that necessary ordering withing a pass is met
Assert.IsTrue(cookersByPass[1].IndexOf(dataCookerD) < cookersByPass[1].IndexOf(dataCookerE));
}
}
}

Просмотреть файл

@ -0,0 +1,300 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class SourceSessionTests
{
[TestMethod]
[UnitTest]
public void Constructor_NullSourceParser_Throws()
{
Assert.ThrowsException<ArgumentNullException>(
() => new SourceProcessingSession<TestRecord, TestParserContext, int>(null));
}
[TestMethod]
[UnitTest]
public void Constructor_NullSourceParserId_Throws()
{
var sourceParser = new TestSourceParser();
Assert.ThrowsException<ArgumentNullException>(
() => new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser));
}
[TestMethod]
[UnitTest]
public void Constructor()
{
var sourceParser = new TestSourceParser() {Id = "TestSourceParser"};
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
Assert.AreEqual(sourceSession.SourceParser, sourceParser);
}
[TestMethod]
[UnitTest]
public void RegisterSourceDataCooker_NullCooker_Throws()
{
var sourceParser = new TestSourceParser() { Id = "TestSourceParser" };
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
Assert.ThrowsException<ArgumentNullException>(
() => sourceSession.RegisterSourceDataCooker(null));
}
[TestMethod]
[UnitTest]
public void RegisterSourceDataCooker()
{
var sourceParser = new TestSourceParser() { Id = "TestSourceParser" };
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
var sourceDataCooker = new TestSourceDataCooker()
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker")
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker);
Assert.AreEqual(sourceSession.RegisteredSourceDataCookers.Count, 1);
var returnedSourceDataCooker = sourceSession.GetSourceDataCooker(sourceDataCooker.Path);
Assert.AreEqual(sourceDataCooker, returnedSourceDataCooker);
}
[TestMethod]
[UnitTest]
public void ProcessSession()
{
var sourceParser = new TestSourceParser() { Id = "TestSourceParser" };
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
var dataCookerContext = new TestSourceDataCookerContext();
var sourceDataCooker1 = new TestSourceDataCooker(dataCookerContext)
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker1"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 13, 45, 67 }),
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker1);
var sourceDataCooker2 = new TestSourceDataCooker(dataCookerContext)
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker2"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 113, 145, 167, 1000 }),
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker2);
var sourceDataCooker3 = new TestSourceDataCooker(dataCookerContext)
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker3"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 200, 250, 286, 1000 }),
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker3);
var sourceDataCooker4 = new TestSourceDataCooker(dataCookerContext)
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker4"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 145, 316, 315, 301 }),
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker4);
// Setup some dependencies so that not all cookers are in the same pass/block
// Note that changing the required cookers after registering with the source
// session should be fine, as the cookers aren't scheduled until ProcessSource
// is called on the source session.
//
{
// sourceDataCooker4: Pass=1, Block=0
sourceDataCooker4.RequiredDataCookers = new ReadOnlyCollection<DataCookerPath>(
new[] { sourceDataCooker3.Path });
// sourceDataCooker2: Pass=1, Block=1
sourceDataCooker2.RequiredDataCookers = new ReadOnlyCollection<DataCookerPath>(
new []
{
sourceDataCooker4.Path,
sourceDataCooker1.Path
});
sourceDataCooker2.DependencyTypes = new ReadOnlyDictionary<DataCookerPath, DataCookerDependencyType>(
new Dictionary<DataCookerPath, DataCookerDependencyType>()
{
{sourceDataCooker4.Path, DataCookerDependencyType.AsConsumed }
});
}
// data cooker order should be: { cooker1, cooker3 }, { cooker4, cooker2 }
// cooker1 and cooker3 could be in any order in pass 0.
// but cooker4 and cooker2 are ordered as such in pass 1.
//
// setup data for the TestParser to "parse"
var testRecords = new List<TestRecord>
{
// delivered to cooker4 first and then cooker2 in Pass1
new TestRecord {Key = 145, Value = "145"},
// shouldn't be delivered to any cooker
new TestRecord {Key = 500, Value = "500"},
// delivered to cooker3 in Pass0
new TestRecord {Key = 250, Value = "250:1"},
// delivered to cooker1 in Pass0
new TestRecord {Key = 67, Value = "67"},
// delivered to cooker3 in Pass0 and cooker2 in Pass1
new TestRecord {Key = 1000, Value = "1000"},
// delivered to cooker3 in Pass0
new TestRecord {Key = 250, Value = "250:2"},
// delivered to cooker4 in Pass1
new TestRecord {Key = 301, Value = "301"},
// delivered to cooker3 in Pass0
new TestRecord {Key = 286, Value = "286"},
// delivered to cooker4 in Pass1
new TestRecord {Key = 315, Value = "315"},
};
sourceParser.TestRecords = testRecords;
Assert.AreEqual(sourceSession.RegisteredSourceDataCookers.Count, 4);
sourceSession.ProcessSource(new NullLogger(), new TestProgress(), CancellationToken.None);
// Make sure each cooker received a BeginDataCooking call
Assert.AreEqual(sourceDataCooker1.BeginDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker2.BeginDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker3.BeginDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker4.BeginDataCookingCallCount, 1);
// Make sure each cooker received a EndDataCooking call
Assert.AreEqual(sourceDataCooker1.EndDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker2.EndDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker3.EndDataCookingCallCount, 1);
Assert.AreEqual(sourceDataCooker4.EndDataCookingCallCount, 1);
// Check that each cooker received the proper number of data records
Assert.AreEqual(sourceDataCooker1.CookDataElementCallCount, 1);
Assert.AreEqual(sourceDataCooker2.CookDataElementCallCount, 2);
Assert.AreEqual(sourceDataCooker3.CookDataElementCallCount, 4);
Assert.AreEqual(sourceDataCooker4.CookDataElementCallCount, 3);
// Check that each cooker received the expected data records
Assert.IsTrue(sourceDataCooker1.ReceivedRecords.ContainsKey(testRecords[3]));
Assert.IsTrue(sourceDataCooker2.ReceivedRecords.ContainsKey(testRecords[0]));
Assert.IsTrue(sourceDataCooker2.ReceivedRecords.ContainsKey(testRecords[4]));
Assert.IsTrue(sourceDataCooker3.ReceivedRecords.ContainsKey(testRecords[2]));
Assert.IsTrue(sourceDataCooker3.ReceivedRecords.ContainsKey(testRecords[4]));
Assert.IsTrue(sourceDataCooker3.ReceivedRecords.ContainsKey(testRecords[5]));
Assert.IsTrue(sourceDataCooker3.ReceivedRecords.ContainsKey(testRecords[7]));
Assert.IsTrue(sourceDataCooker4.ReceivedRecords.ContainsKey(testRecords[0]));
Assert.IsTrue(sourceDataCooker4.ReceivedRecords.ContainsKey(testRecords[6]));
Assert.IsTrue(sourceDataCooker4.ReceivedRecords.ContainsKey(testRecords[8]));
// Make sure that cooker4 received test record 0 before cooker2
Assert.IsTrue(sourceDataCooker4.ReceivedRecords[testRecords[0]] < sourceDataCooker2.ReceivedRecords[testRecords[0]]);
// TestRecord 145 should be delivered twice
// TestRecord 1000 should be delivered twice
// TestRecord 500 should not ever be delivered
int expectedReceivedCount = testRecords.Count + 2 - 1;
Assert.AreEqual(expectedReceivedCount, dataCookerContext.CountOfTestRecordsReceived);
}
[TestMethod]
[UnitTest]
public void RegisteredDataKeysAreSentToSourceParser()
{
var sourceParser = new TestSourceParser() { Id = "TestSourceParser" };
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
var sourceDataCooker1 = new TestSourceDataCooker()
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker1"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 13, 45, 67 })
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker1);
var sourceDataCooker2 = new TestSourceDataCooker()
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker2"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 113, 145, 167, 1000 }),
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker2);
// setup data for the TestParser to "parse"
var testRecords = new List<TestRecord>
{
// delivered to cooker4 first and then cooker2 in Pass1
new TestRecord {Key = 145, Value = "145"},
};
sourceParser.TestRecords = testRecords;
sourceSession.ProcessSource(new NullLogger(), new TestProgress(), CancellationToken.None);
Assert.IsFalse(sourceParser.ReceivedAllEventsConsumed);
Assert.IsTrue(
sourceParser.RequestedDataKeys.Count ==
sourceDataCooker1.DataKeys.Count + sourceDataCooker2.DataKeys.Count);
}
[TestMethod]
[UnitTest]
public void RegisteredDataKeysAreSentToSourceParserWithAllEventsTrue()
{
var sourceParser = new TestSourceParser() { Id = "TestSourceParser" };
var sourceSession = new SourceProcessingSession<TestRecord, TestParserContext, int>(sourceParser);
var sourceDataCooker1 = new TestSourceDataCooker()
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker1"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() { 13, 45, 67 })
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker1);
var sourceDataCooker2 = new TestSourceDataCooker()
{
Path = new DataCookerPath(sourceParser.Id, "TestSourceDataCooker2"),
DataKeys = new ReadOnlyHashSet<int>(new HashSet<int>() {}),
Options = SDK.Extensibility.DataCooking.SourceDataCooking.SourceDataCookerOptions.ReceiveAllDataElements,
};
sourceSession.RegisterSourceDataCooker(sourceDataCooker2);
// setup data for the TestParser to "parse"
var testRecords = new List<TestRecord>
{
// delivered to cooker4 first and then cooker2 in Pass1
new TestRecord {Key = 145, Value = "145"},
};
sourceParser.TestRecords = testRecords;
sourceSession.ProcessSource(new NullLogger(), new TestProgress(), CancellationToken.None);
Assert.IsTrue(sourceParser.ReceivedAllEventsConsumed);
Assert.IsTrue(sourceParser.RequestedDataKeys.Count == sourceDataCooker1.DataKeys.Count);
}
}
}

Просмотреть файл

@ -0,0 +1,301 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Tables;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility
{
[TestClass]
public class TableExtensionSelectorTests
{
// Source Cookers
private static TestSourceDataCookerReference[] SourceCookers;
private static readonly IDictionary<DataCookerPath, TestSourceDataCookerReference> SourceCookersByPath
= new Dictionary<DataCookerPath, TestSourceDataCookerReference>();
// Composite cookers
private static TestCompositeDataCookerReference[] CompositeCookers;
private static readonly IDictionary<DataCookerPath, TestCompositeDataCookerReference> CompositeCookersByPath
= new Dictionary<DataCookerPath, TestCompositeDataCookerReference>();
private static readonly DataCookerPath Source1Cooker1Path = new DataCookerPath("Source1", "SourceCooker1");
static TableExtensionSelectorTests()
{
SetupSourceCookers();
SetupCompositeCookers();
}
private static void SetupSourceCookers()
{
SourceCookers = new[]
{
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source1", "SourceCooker1"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source2", "SourceCooker0"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source2", "SourceCooker10"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source2", "SourceCooker13"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source1", "SourceCooker41"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source1", "SourceCooker356"),
},
new TestSourceDataCookerReference
{
Path = new DataCookerPath("Source4", "SourceCooker_Woah!"),
},
};
foreach (var sourceCooker in SourceCookers)
{
SourceCookersByPath.Add(sourceCooker.Path, sourceCooker);
}
}
private static void SetupCompositeCookers()
{
CompositeCookers = new[]
{
new TestCompositeDataCookerReference
{
Path = new DataCookerPath(string.Empty, "CompositeCooker1"),
},
new TestCompositeDataCookerReference
{
Path = new DataCookerPath(string.Empty, "CompositeCooker2"),
},
new TestCompositeDataCookerReference
{
Path = new DataCookerPath(string.Empty, "CompositeCooker3"),
},
new TestCompositeDataCookerReference
{
Path = new DataCookerPath(string.Empty, "CompositeCooker33"),
},
};
foreach (var cooker in CompositeCookers)
{
CompositeCookersByPath.Add(cooker.Path, cooker);
}
}
private static void AddRequiredDataExtensions(
TestTableExtensionReference table,
TestDataExtensionRepository testRepo)
{
foreach (var cookerPath in table.RequiredDataCookers)
{
if (SourceCookersByPath.TryGetValue(cookerPath, out var sourceCookerReference))
{
testRepo.sourceCookersByPath.Add(cookerPath, sourceCookerReference);
}
else if (CompositeCookersByPath.TryGetValue(cookerPath, out var compositeCookerReference))
{
testRepo.compositeCookersByPath.Add(cookerPath, compositeCookerReference);
}
}
if (table.DependencyState != null)
{
testRepo.FinalizeDataExtensions();
}
}
/// <summary>
/// Add a single table, make sure it and its category are added to the table selector
/// </summary>
[TestMethod]
[UnitTest]
public void ConstructorTest1()
{
var table1 = new TestTableExtensionReference(false)
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{ADE569AF-4460-4DCC-A7C8-9748737AE592}"),
"Table1",
"Table1 Description",
"General",
false,
TableLayoutStyle.GraphAndTable,
new[] { Source1Cooker1Path }),
BuildTableAction = (tableBuilder, dataRetrieval) => {},
availability = DataExtensionAvailability.Available,
};
var testRepo = new TestDataExtensionRepository();
testRepo.tablesById.Add(table1.TableDescriptor.Guid, table1);
var tableSelector = new TableExtensionSelector(testRepo);
Assert.AreEqual(tableSelector.Tables.Count, 1);
Assert.AreEqual(tableSelector.TableCategories.Count, 1);
Assert.IsTrue(tableSelector.TableCategories.Contains(table1.TableDescriptor.Category));
}
/// <summary>
/// Add a single, unavailable table.
/// Make sure neither it nor its category are added to the table selector
/// </summary>
[TestMethod]
[UnitTest]
public void ConstructorTest2()
{
var table1 = new TestTableExtensionReference(false)
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{ADE569AF-4460-4DCC-A7C8-9748737AE592}"),
"Table1",
"Table1 Description",
"General",
false,
TableLayoutStyle.GraphAndTable,
new[] { Source1Cooker1Path }),
BuildTableAction = (tableBuilder, dataRetrieval) => {},
availability = DataExtensionAvailability.MissingRequirement,
};
var testRepo = new TestDataExtensionRepository();
testRepo.tablesById.Add(table1.TableDescriptor.Guid, table1);
var tableSelector = new TableExtensionSelector(testRepo);
Assert.AreEqual(tableSelector.Tables.Count, 0);
Assert.AreEqual(tableSelector.TableCategories.Count, 0);
}
/// <summary>
/// With multiple tables, make sure the categories show up correctly.
/// All categories should be available.
/// There should be no duplicate category names.
/// </summary>
[TestMethod]
[UnitTest]
public void ConstructorTest3()
{
var table1 = new TestTableExtensionReference(false)
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{ADE569AF-4460-4DCC-A7C8-9748737AE592}"),
"Table1",
"Table1 Description",
"General",
false,
TableLayoutStyle.GraphAndTable,
new[] { Source1Cooker1Path }),
BuildTableAction = (tableBuilder, dataRetrieval) => { },
availability = DataExtensionAvailability.Available,
};
var table2 = new TestTableExtensionReference(false)
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{42F33CCB-D1D4-48CD-BAF0-853A508C638D}"),
"Table2",
"Table1 Description",
"Specific",
false,
TableLayoutStyle.GraphAndTable,
new[] { Source1Cooker1Path }),
BuildTableAction = (tableBuilder, dataRetrieval) => { },
availability = DataExtensionAvailability.Available,
};
var table3 = new TestTableExtensionReference(false)
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{3A613426-6550-49AA-89F0-C6BBB6039EB4}"),
"Table3",
"Table1 Description",
"General",
false,
TableLayoutStyle.GraphAndTable,
new[] { Source1Cooker1Path }),
BuildTableAction = (tableBuilder, dataRetrieval) => { },
availability = DataExtensionAvailability.Available,
};
var testRepo = new TestDataExtensionRepository();
testRepo.tablesById.Add(table1.TableDescriptor.Guid, table1);
testRepo.tablesById.Add(table2.TableDescriptor.Guid, table2);
testRepo.tablesById.Add(table3.TableDescriptor.Guid, table3);
var tableSelector = new TableExtensionSelector(testRepo);
Assert.AreEqual(tableSelector.Tables.Count, 3);
Assert.AreEqual(tableSelector.TableCategories.Count, 2);
Assert.IsTrue(tableSelector.TableCategories.Contains(table2.TableDescriptor.Category));
Assert.IsTrue(tableSelector.TableCategories.Contains(table1.TableDescriptor.Category));
}
/// <summary>
/// Add a single table, with a single source cooker.
/// Make sure the single source cooker is returned.
/// </summary>
[TestMethod]
[UnitTest]
public void GetSourceDataCookersForTablesTest1()
{
var requiredDataCookers = new List<DataCookerPath>
{
Source1Cooker1Path,
};
var table1 = new TestTableExtensionReference
{
TableDescriptor = new TableDescriptor(
Guid.Parse("{ADE569AF-4460-4DCC-A7C8-9748737AE592}"),
"Table1",
"Table1 Description",
"General",
false,
TableLayoutStyle.GraphAndTable,
requiredDataCookers),
BuildTableAction = (tableBuilder, dataRetrieval) => { },
};
var testRepo = new TestDataExtensionRepository();
testRepo.tablesById.Add(table1.TableDescriptor.Guid, table1);
AddRequiredDataExtensions(table1, testRepo);
var tableSelector = new TableExtensionSelector(testRepo);
Assert.AreEqual(tableSelector.Tables.Count, 1);
var requiredSourceDataCookers = tableSelector.GetSourceDataCookersForTables(new [] {table1.TableDescriptor.Guid });
Assert.AreEqual(requiredSourceDataCookers.Count, 1);
var sourceId = requiredDataCookers[0].SourceParserId;
Assert.IsTrue(requiredSourceDataCookers.ContainsKey(sourceId));
Assert.AreEqual(requiredSourceDataCookers[sourceId].Count(), 1);
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestApplicationEnvironment
: IApplicationEnvironment
{
public string ApplicationName { get; set; }
public string RuntimeName { get; set; }
public bool GraphicalUserEnvironment { get; set; }
public ISerializer Serializer { get; set; }
public ITableDataSynchronization TableDataSynchronizer { get; set; }
public bool VerboseOutput { get; set; }
public ISourceDataCookerFactoryRetrieval SourceDataCookerFactoryRetrieval { get; set; }
public ISourceSessionFactory SourceSessionFactory { get; set; }
public void DisplayMessage(MessageType messageType, IFormatProvider formatProvider, string format, params object[] args)
{
}
public ButtonResult MessageBox(MessageType messageType, IFormatProvider formatProvider, Buttons buttons, string caption, string format, params object[] args)
{
return ButtonResult.None;
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestCookedDataRetrieval
: ICookedDataRetrieval
{
public T QueryOutput<T>(DataOutputPath identifier)
{
return (T) QueryOutput(identifier);
}
public Func<DataOutputPath, object> queryOutputFunc;
public object QueryOutput(DataOutputPath identifier)
{
return this.queryOutputFunc?.Invoke(identifier);
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestCustomDataProcessor
: CustomDataProcessorBaseWithSourceParser<TestRecord, TestParserContext, int>
{
internal static TestCustomDataProcessor CreateTestCustomDataProcessor(
string sourceParserId = "TestSourceParser")
{
var sourceParser = new TestSourceParser() { Id = sourceParserId };
var applicationEnvironment = new TestApplicationEnvironment();
var processorEnvironment = new TestProcessorEnvironment();
var dataExtensionRepo = new TestDataExtensionRepository();
CustomDataProcessorExtensibilitySupport extensibilitySupport = null;
applicationEnvironment.SourceSessionFactory = new TestSourceSessionFactory();
processorEnvironment.CreateDataProcessorExtensibilitySupportFunc = (processor) =>
{
extensibilitySupport = new CustomDataProcessorExtensibilitySupport(processor, dataExtensionRepo);
return extensibilitySupport;
};
// this will create the CustomDataProcessorExtensibilitySupport
var cdp = new TestCustomDataProcessor(
sourceParser,
ProcessorOptions.Default,
applicationEnvironment,
processorEnvironment,
new Dictionary<TableDescriptor, Action<ITableBuilder, IDataExtensionRetrieval>>(),
new List<TableDescriptor>());
Assert.IsNotNull(extensibilitySupport);
cdp.ExtensibilitySupport = extensibilitySupport;
cdp.ExtensionRepository = dataExtensionRepo;
return cdp;
}
public TestCustomDataProcessor(
ISourceParser<TestRecord, TestParserContext, int> sourceParser,
ProcessorOptions options,
IApplicationEnvironment applicationEnvironment,
IProcessorEnvironment processorEnvironment,
IReadOnlyDictionary<TableDescriptor, Action<ITableBuilder, IDataExtensionRetrieval>> allTablesMapping,
IEnumerable<TableDescriptor> metadataTables)
: base(sourceParser, options, applicationEnvironment, processorEnvironment, allTablesMapping, metadataTables)
{
}
public CustomDataProcessorExtensibilitySupport ExtensibilitySupport { get; set;}
public TestDataExtensionRepository ExtensionRepository { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,123 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataProcessors;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Dependency;
using DataCookerPath = Microsoft.Performance.SDK.Extensibility.DataCookerPath;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
internal abstract class TestDataCookerReference
: TestDataExtensionReference,
IDataCookerReference
{
protected TestDataCookerReference()
: base()
{
}
protected TestDataCookerReference(bool useDataExtensionDependencyState)
: base(useDataExtensionDependencyState)
{
}
public string Description { get; set; }
public DataCookerPath Path { get; set; }
public override string Name => this.Path.CookerPath;
}
internal class TestSourceDataCookerReference
: TestDataCookerReference,
ISourceDataCookerReference
{
public TestSourceDataCookerReference()
: base()
{
}
public TestSourceDataCookerReference(bool useDataExtensionDependencyState)
:base(useDataExtensionDependencyState)
{
}
public Func<IDataCookerDescriptor> createInstance;
public IDataCookerDescriptor CreateInstance()
{
return this.createInstance?.Invoke();
}
public bool UseDefaultValidation { get; set; } = true;
public override void PerformAdditionalDataExtensionValidation(
IDataExtensionDependencyStateSupport dependencyStateSupport,
IDataExtensionReference requiredDataExtension)
{
if (this.UseDefaultValidation)
{
this.DefaultAdditionalValidation(dependencyStateSupport, requiredDataExtension);
}
else
{
base.PerformAdditionalDataExtensionValidation(
dependencyStateSupport,
requiredDataExtension);
}
}
// this matches what we have in SourceDataCookerReference - it's not 100% necessary,
// but it's a nice to have for testing some scenarios
//
public void DefaultAdditionalValidation(
IDataExtensionDependencyStateSupport dependencyStateSupport,
IDataExtensionReference reference)
{
if (reference is IDataCookerReference dataCookerReference)
{
if (!StringComparer.Ordinal.Equals(dataCookerReference.Path.SourceParserId, this.Path.SourceParserId))
{
dependencyStateSupport.AddError("Wrong source parser!");
dependencyStateSupport.UpdateAvailability(DataExtensionAvailability.Error);
}
}
else if (reference is IDataProcessorReference dataProcessorReference)
{
dependencyStateSupport.AddError(
$"A source data cooker may not depend on a data processor: {dataProcessorReference.Id}");
dependencyStateSupport.UpdateAvailability(DataExtensionAvailability.Error);
}
else
{
dependencyStateSupport.AddError(
$"A requested dependency on an unknown data extension type is not supported: {reference.Name}");
dependencyStateSupport.UpdateAvailability(DataExtensionAvailability.Error);
}
}
}
internal class TestCompositeDataCookerReference
: TestDataCookerReference,
ICompositeDataCookerReference
{
public TestCompositeDataCookerReference()
: this(true)
{
}
public TestCompositeDataCookerReference(bool useDataExtensionDependencyState)
: base(useDataExtensionDependencyState)
{
}
public IDataCooker GetOrCreateInstance(IDataExtensionRetrieval requiredData)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Dependency;
using System.Collections.Generic;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestDataExtensionDependencies
: IDataExtensionDependencies
{
public TestDataExtensionDependencies()
{
this.requiredSourceDataCookersByPath = new HashSet<DataCookerPath>();
this.requiredCompositeDataCookersByPath = new HashSet<DataCookerPath>();
this.requiredDataProcessorsById = new HashSet<DataProcessorId>();
}
public HashSet<DataCookerPath> requiredSourceDataCookersByPath;
public IReadOnlyCollection<DataCookerPath> RequiredSourceDataCookerPaths => this.requiredSourceDataCookersByPath;
public HashSet<DataCookerPath> requiredCompositeDataCookersByPath;
public IReadOnlyCollection<DataCookerPath> RequiredCompositeDataCookerPaths => this.requiredCompositeDataCookersByPath;
public HashSet<DataProcessorId> requiredDataProcessorsById;
public IReadOnlyCollection<DataProcessorId> RequiredDataProcessorIds => this.requiredDataProcessorsById;
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Dependency;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestDataExtensionDependencyTarget
: IDataExtensionDependencyTarget
{
public List<DataCookerPath> requiredDataCookers = new List<DataCookerPath>();
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers => new ReadOnlyCollection<DataCookerPath>(this.requiredDataCookers);
public List<DataProcessorId> requiredDataProcessors = new List<DataProcessorId>();
public IReadOnlyCollection<DataProcessorId> RequiredDataProcessors => new ReadOnlyCollection<DataProcessorId>(this.requiredDataProcessors);
public string Name { get; set; }
public DataExtensionAvailability InitialAvailability { get; set; }
public Action<IDataExtensionDependencyStateSupport, IDataExtensionReference> validationAction = null;
public void PerformAdditionalDataExtensionValidation(
IDataExtensionDependencyStateSupport dependencyStateSupport,
IDataExtensionReference requiredDataCooker)
{
this.validationAction?.Invoke(dependencyStateSupport, requiredDataCooker);
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Dependency;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Repository;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
internal abstract class TestDataExtensionReference
: IDataExtensionReference
{
protected TestDataExtensionReference()
: this(true)
{
}
protected TestDataExtensionReference(bool useDataExtensionDependencyState)
{
if (useDataExtensionDependencyState)
{
this.DependencyState = new DataExtensionDependencyState(this);
}
}
public HashSet<DataCookerPath> requiredDataCookers = new HashSet<DataCookerPath>();
public virtual IReadOnlyCollection<DataCookerPath> RequiredDataCookers => this.requiredDataCookers;
public HashSet<DataProcessorId> requiredDataProcessors = new HashSet<DataProcessorId>();
public virtual IReadOnlyCollection<DataProcessorId> RequiredDataProcessors => this.requiredDataProcessors;
public virtual string Name { get; }
public DataExtensionAvailability InitialAvailability { get; set; }
public Action<IDataExtensionDependencyStateSupport, IDataExtensionReference> performAdditionalDataExtensionValidation;
public virtual void PerformAdditionalDataExtensionValidation(
IDataExtensionDependencyStateSupport dependencyStateSupport,
IDataExtensionReference requiredDataExtension)
{
this.performAdditionalDataExtensionValidation?.Invoke(dependencyStateSupport, requiredDataExtension);
}
public DataExtensionAvailability availability = DataExtensionAvailability.Undetermined;
public DataExtensionAvailability Availability
{
get
{
if (this.DependencyState != null)
{
return this.DependencyState.Availability;
}
return this.availability;
}
}
public TestDataExtensionDependencies dependencyRetrieval = new TestDataExtensionDependencies();
public IDataExtensionDependencies DependencyReferences
{
get
{
if (this.DependencyState != null)
{
return this.DependencyState.DependencyReferences;
}
return this.dependencyRetrieval;
}
}
public Action<IDataExtensionRepository> processDependencies;
public void ProcessDependencies(
IDataExtensionRepository availableDataExtensions)
{
if (this.processDependencies != null)
{
this.processDependencies.Invoke(availableDataExtensions);
}
else if (this.DependencyState != null)
{
this.DependencyState.ProcessDependencies(availableDataExtensions);
}
}
public DataExtensionDependencyState DependencyState { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataProcessors;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Repository;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Tables;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestDataExtensionRepository
: IDataExtensionRepository
{
public HashSet<ISourceDataCookerFactory> sourceDataCookerReferences = new HashSet<ISourceDataCookerFactory>();
public IReadOnlyCollection<ISourceDataCookerFactory> GetSourceDataCookers(string sourceParserId)
{
return this.sourceDataCookerReferences;
}
public ISourceDataCookerFactory GetSourceDataCookerFactory(DataCookerPath dataCookerPath)
{
return this.GetSourceDataCookerReference(dataCookerPath);
}
public Dictionary<DataCookerPath, ISourceDataCookerReference> sourceCookersByPath = new Dictionary<DataCookerPath, ISourceDataCookerReference>();
public Func<DataCookerPath, ISourceDataCookerReference> getSourceDataCooker;
public ISourceDataCookerReference GetSourceDataCookerReference(DataCookerPath dataCookerPath)
{
if (this.sourceCookersByPath.TryGetValue(dataCookerPath, out var sourceCookerReference))
{
return sourceCookerReference;
}
return this.getSourceDataCooker?.Invoke(dataCookerPath);
}
public Dictionary<DataCookerPath, ICompositeDataCookerReference> compositeCookersByPath
= new Dictionary<DataCookerPath, ICompositeDataCookerReference>();
public Func<DataCookerPath, ICompositeDataCookerReference> getCompositeDataCooker;
public ICompositeDataCookerReference GetCompositeDataCookerReference(DataCookerPath dataCookerPath)
{
if (this.compositeCookersByPath.TryGetValue(dataCookerPath, out var cookerReference))
{
return cookerReference;
}
if (this.getCompositeDataCooker != null)
{
return this.getCompositeDataCooker.Invoke(dataCookerPath);
}
return null;
}
public Dictionary<Guid, ITableExtensionReference> tablesById = new Dictionary<Guid, ITableExtensionReference>();
public IReadOnlyDictionary<Guid, ITableExtensionReference> TablesById => this.tablesById;
public IEnumerable<DataCookerPath> SourceDataCookers => throw new NotImplementedException();
public IEnumerable<DataCookerPath> CompositeDataCookers => throw new NotImplementedException();
public IEnumerable<DataProcessorId> DataProcessors => throw new NotImplementedException();
public Dictionary<DataProcessorId, IDataProcessorReference> dataProcessorsById = new Dictionary<DataProcessorId, IDataProcessorReference>();
public Func<DataProcessorId, IDataProcessorReference> getDataProcessor;
public IDataProcessorReference GetDataProcessorReference(DataProcessorId dataProcessorId)
{
if (this.dataProcessorsById.TryGetValue(dataProcessorId, out var processorReference))
{
return processorReference;
}
return this.getDataProcessor?.Invoke(dataProcessorId);
}
public void FinalizeDataExtensions()
{
foreach (var dataCookerReference in this.sourceCookersByPath)
{
dataCookerReference.Value.ProcessDependencies(this);
}
foreach (var dataCookerReference in this.compositeCookersByPath)
{
dataCookerReference.Value.ProcessDependencies(this);
}
foreach (var dataProcessorReference in this.dataProcessorsById)
{
dataProcessorReference.Value.ProcessDependencies(this);
}
foreach (var tableReferenceBuilder in this.TablesById)
{
tableReferenceBuilder.Value.ProcessDependencies(this);
}
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataProcessing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataProcessors;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
internal class TestDataProcessorReference
: TestDataExtensionReference,
IDataProcessorReference
{
public TestDataProcessorReference()
: base()
{
}
public TestDataProcessorReference(bool useDataExtensionDependencyState)
: base(useDataExtensionDependencyState)
{
}
public Func<IDataExtensionRetrieval, IDataProcessor> getOrCreateInstance;
public IDataProcessor GetOrCreateInstance(IDataExtensionRetrieval requiredData)
{
return this.getOrCreateInstance?.Invoke(requiredData);
}
public string Id { get; set; }
public string Description { get; set; }
public override string Name => this.Id;
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Testing.SDK;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public sealed class TestProcessorEnvironment
: IProcessorEnvironment
{
public void AddNewTable(IDynamicTableBuilder dynamicTableBuilder)
{
}
public Func<ICustomDataProcessorWithSourceParser, IDataProcessorExtensibilitySupport> CreateDataProcessorExtensibilitySupportFunc
{ get; set; }
public IDataProcessorExtensibilitySupport CreateDataProcessorExtensibilitySupport(ICustomDataProcessorWithSourceParser processor)
{
return CreateDataProcessorExtensibilitySupportFunc?.Invoke(processor);
}
public ILogger CreateLogger(Type processorType)
{
return new NullLogger();
}
public IDynamicTableBuilder RequestDynamicTableBuilder(TableDescriptor descriptor)
{
return new DynamicTableBuilder(this.AddNewTable, descriptor, new DataSourceInfo(0, 0, DateTime.UnixEpoch));
}
}
}

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestSourceDataCookerContext
{
public int CountOfTestRecordsReceived;
}
public class TestSourceDataCooker
: ISourceDataCooker<TestRecord, TestParserContext, int>
{
public TestSourceDataCooker()
{
this.Context = new TestSourceDataCookerContext();
}
public TestSourceDataCooker(TestSourceDataCookerContext context)
{
this.Context = context;
}
public string Description { get; set; }
public T QueryOutput<T>(DataOutputPath identifier)
{
throw new System.NotImplementedException();
}
public object QueryOutput(DataOutputPath identifier)
{
throw new System.NotImplementedException();
}
public IReadOnlyCollection<DataOutputPath> OutputIdentifiers { get; set; }
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers { get; set; }
public IReadOnlyDictionary<DataCookerPath, DataCookerDependencyType> DependencyTypes { get; set; }
public DataProductionStrategy DataProductionStrategy { get; set; }
public ReadOnlyHashSet<int> DataKeys { get; set; }
public SourceDataCookerOptions Options { get; set; }
public int BeginDataCookingCallCount { get; private set; } = 0;
public Action<CancellationToken> BeginDataCookingAction { get; set; } = null;
public void BeginDataCooking(ICookedDataRetrieval dependencyRetrieval, CancellationToken cancellationToken)
{
this.BeginDataCookingCallCount++;
this.BeginDataCookingAction?.Invoke(cancellationToken);
}
public int CookDataElementCallCount { get; private set; } = 0;
public Func<TestRecord, TestParserContext, CancellationToken, DataProcessingResult> CookDataElementFunc { get; set; } = null;
public DataProcessingResult CookDataElementResult { get; set; } = DataProcessingResult.Processed;
public Dictionary<TestRecord, int> ReceivedRecords = new Dictionary<TestRecord, int>();
public DataProcessingResult CookDataElement(TestRecord data, TestParserContext context, CancellationToken cancellationToken)
{
this.ReceivedRecords.Add(data, this.Context.CountOfTestRecordsReceived);
this.Context.CountOfTestRecordsReceived++;
this.CookDataElementCallCount++;
if (this.CookDataElementFunc != null)
{
return this.CookDataElementFunc(data, context, cancellationToken);
}
return this.CookDataElementResult;
}
public int EndDataCookingCallCount { get; private set; } = 0;
public Action<CancellationToken> EndDataCookingAction { get; set; } = null;
public void EndDataCooking(CancellationToken cancellationToken)
{
this.EndDataCookingCallCount++;
this.EndDataCookingAction?.Invoke(cancellationToken);
}
public DataCookerPath Path { get; set; }
//public int CountOfTestRecordsReceived = 0;
public TestSourceDataCookerContext Context { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Tests.Extensibility.DataTypes;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
/// <inheritdoc />
public class TestSourceParser
: ISourceParser<TestRecord, TestParserContext, int>
{
/// <inheritdoc />
public string Id { get; set; }
/// <inheritdoc />
public Type DataElementType { get; set; }
/// <inheritdoc />
public Type DataContextType { get; set; }
/// <inheritdoc />
public Type DataKeyType { get; set; }
/// <inheritdoc />
public int MaxSourceParseCount { get; set; }
public bool ReceivedAllEventsConsumed { get; set; }
public IReadOnlyCollection<int> RequestedDataKeys { get; set; }
/// <inheritdoc/>
public virtual void PrepareForProcessing(bool allEventsConsumed, IReadOnlyCollection<int> requestedDataKeys)
{
this.ReceivedAllEventsConsumed = allEventsConsumed;
this.RequestedDataKeys = requestedDataKeys;
}
/// <inheritdoc />
public void ProcessSource(
ISourceDataProcessor<TestRecord, TestParserContext, int> dataProcessor,
ILogger logger,
IProgress<int> progress,
CancellationToken cancellationToken)
{
if (this.TestRecords == null)
{
return;
}
foreach (var testRecord in this.TestRecords)
{
dataProcessor.ProcessDataElement(testRecord, new TestParserContext(), cancellationToken);
}
progress.Report(100);
}
/// <inheritdoc />
public DataSourceInfo DataSourceInfo { get; set; }
public IEnumerable<TestRecord> TestRecords { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestSourceProcessingSession<T, TContext, TKey>
: ISourceProcessingSession<T, TContext, TKey>
where T : IKeyedDataType<TKey>
{
public ICustomDataProcessorWithSourceParser<T, TContext, TKey> CustomDataProcessor { get; set; }
public string Id { get; set; }
public Type DataElementType { get; set; } = typeof(T);
public Type DataContextType { get; set; } = typeof(TContext);
public Type DataKeyType { get; set; } = typeof(TKey);
public int MaxSourceParseCount { get; set; } = int.MaxValue;
public List<ISourceDataCooker<T, TContext, TKey>> SourceDataCookers = new List<ISourceDataCooker<T, TContext, TKey>>();
public IReadOnlyCollection<ISourceDataCooker<T, TContext, TKey>> RegisteredSourceDataCookers =>
this.SourceDataCookers;
public Func<DataCookerPath, ISourceDataCooker<T, TContext, TKey>> GetSourceDataCookerFunc { get; set; }
public ISourceDataCooker<T, TContext, TKey> GetSourceDataCooker(DataCookerPath cookerPath)
{
return this.GetSourceDataCookerFunc?.Invoke(cookerPath);
}
public Func<T, TContext, CancellationToken, DataProcessingResult> ProcessDataElementFunc { get; set; }
public DataProcessingResult ProcessDataElementResult { get; set; } = DataProcessingResult.Ignored;
public DataProcessingResult ProcessDataElement(T data, TContext context, CancellationToken cancellationToken)
{
return ProcessDataElementFunc?.Invoke(data, context, cancellationToken) ?? this.ProcessDataElementResult;
}
public Action<ILogger, IProgress<int>, CancellationToken> ProcessSourceAction { get; set; }
public void ProcessSource(ILogger logger, IProgress<int> progress, CancellationToken cancellationToken)
{
this.ProcessSourceAction?.Invoke(logger, progress, cancellationToken);
}
public Action<ISourceDataCooker<T, TContext, TKey>> RegisterSourceDataCookerAction { get; set; }
public void RegisterSourceDataCooker(ISourceDataCooker<T, TContext, TKey> dataCooker)
{
this.RegisterSourceDataCookerAction?.Invoke(dataCooker);
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
public class TestSourceSessionFactory
: ISourceSessionFactory
{
public ISourceProcessingSession<T, TContext, TKey> CreateSourceSession<T, TContext, TKey>(
ICustomDataProcessorWithSourceParser<T, TContext, TKey> customDataProcessor)
where T : IKeyedDataType<TKey>
{
return new TestSourceProcessingSession<T, TContext, TKey>() { CustomDataProcessor = customDataProcessor };
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Tables;
namespace Microsoft.Performance.SDK.Runtime.Tests.Extensibility.TestClasses
{
internal class TestTableExtensionReference
: TestDataExtensionReference,
ITableExtensionReference
{
public TestTableExtensionReference()
:base()
{
}
public TestTableExtensionReference(bool useDataExtensionDependencyState)
:base(useDataExtensionDependencyState)
{
}
public TableDescriptor TableDescriptor { get; set; }
public Action<ITableBuilder, IDataExtensionRetrieval> BuildTableAction { get; set; }
public Func<IDataExtensionRetrieval, bool> IsDataAvailableFunc { get; set; }
public override IReadOnlyCollection<DataCookerPath> RequiredDataCookers => this.TableDescriptor.RequiredDataCookers;
public override IReadOnlyCollection<DataProcessorId> RequiredDataProcessors => this.TableDescriptor.RequiredDataProcessors;
}
}

Просмотреть файл

@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class FileExtensionUtilsTests
{
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_Null_ReturnsNull()
{
var ext = FileExtensionUtils.CanonicalizeExtension(null);
Assert.IsNull(ext);
}
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_Empty_ReturnsEmpty()
{
var ext = FileExtensionUtils.CanonicalizeExtension(string.Empty);
Assert.AreEqual(string.Empty, ext);
}
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_Whitespace_ReturnsEmpty()
{
var ext = FileExtensionUtils.CanonicalizeExtension(" \t ");
Assert.AreEqual(string.Empty, ext);
}
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_ExtensionWithDotAtStart_ReturnsUppercased()
{
var original = ".txt";
var expected = original.ToUpperInvariant();
var result = FileExtensionUtils.CanonicalizeExtension(original);
Assert.AreEqual(expected, result);
}
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_ExtensionWithoutDotAtStart_ReturnsWithDotUppercased()
{
var original = "txt";
var expected = $".{original}".ToUpperInvariant();
var result = FileExtensionUtils.CanonicalizeExtension(original);
Assert.AreEqual(expected, result);
}
[TestMethod]
[UnitTest]
public void CanonicalizeExtension_ExtensionWithDotNotAtStart_ReturnsWithDotUppercased()
{
var original = "file.txt";
var expected = $".{original}".ToUpperInvariant();
var result = FileExtensionUtils.CanonicalizeExtension(original);
Assert.AreEqual(expected, result);
}
//
// GetCanonicalExtension
//
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_Null_ReturnsNull()
{
var ext = FileExtensionUtils.GetCanonicalExtension(null);
Assert.IsNull(ext);
}
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_Empty_ReturnsEmpty()
{
var ext = FileExtensionUtils.GetCanonicalExtension(string.Empty);
Assert.AreEqual(string.Empty, ext);
}
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_Whitespace_ReturnsEmpty()
{
var original = " ";
var expected = Path.GetExtension(original);
var ext = FileExtensionUtils.GetCanonicalExtension(original);
Assert.AreEqual(expected, ext);
}
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_ExtensionWithDot_ReturnsUppercased()
{
var original = ".txt";
var expected = original.ToUpperInvariant();
var result = FileExtensionUtils.GetCanonicalExtension(original);
Assert.AreEqual(expected, result);
}
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_ExtensionWithoutDot_ReturnsEmpty()
{
var original = "txt";
var expected = string.Empty;
var result = FileExtensionUtils.GetCanonicalExtension(original);
Assert.AreEqual(expected, result);
}
[TestMethod]
[UnitTest]
public void GetCanonicalExtension_FilenameWithExtension_ReturnsCanonicalExtension()
{
var original = "file.txt";
var expected = ".txt".ToUpperInvariant();
var result = FileExtensionUtils.GetCanonicalExtension(original);
Assert.AreEqual(expected, result);
}
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class MetadataTableBuilderFactoryTests
{
public MetadataTableBuilderFactory Sut { get; set; }
[TestInitialize]
public void Initialize()
{
this.Sut = new MetadataTableBuilderFactory();
}
[TestMethod]
[UnitTest]
public void WhenConstructedHasNoCreatedTables()
{
Assert.AreEqual(0, this.Sut.CreatedTables.Count());
}
[TestMethod]
[UnitTest]
public void WhenCreateCalledReturnsNonNullTable()
{
var descriptor = Any.TableDescriptor();
var created = this.Sut.Create(descriptor);
Assert.IsNotNull(created);
}
[TestMethod]
[UnitTest]
public void WhenTableIsCreatedAddedToCreatedTables()
{
var descriptor = Any.TableDescriptor();
var created = this.Sut.Create(descriptor);
Assert.IsTrue(this.Sut.CreatedTables.Contains(created));
}
[TestMethod]
[UnitTest]
public void CreateReturnsTableBuilderForDescriptor()
{
var descriptor = Any.TableDescriptor();
var created = this.Sut.Create(descriptor);
var toCheck = this.Sut.CreatedTables.SingleOrDefault(x => x.TableDescriptor == descriptor);
Assert.IsNotNull(toCheck);
}
}
}

Просмотреть файл

@ -0,0 +1,129 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class MetadataTableBuilderTests
{
private TableDescriptor Descriptor { get; set; }
private MetadataTableBuilder Sut { get; set; }
[TestInitialize]
public void Initialize()
{
this.Descriptor = Any.TableDescriptor();
this.Sut = new MetadataTableBuilder(this.Descriptor);
}
[TestMethod]
[UnitTest]
public void ConstructorDoesNotAllowNull()
{
Assert.ThrowsException<ArgumentNullException>(
() => new MetadataTableBuilder(null));
}
[TestMethod]
[UnitTest]
public void DescriptionProperlySet()
{
Assert.AreEqual(this.Descriptor.Description, this.Sut.Description);
}
[TestMethod]
[UnitTest]
public void GuidProperlySet()
{
Assert.AreEqual(this.Descriptor.Guid, this.Sut.Guid);
}
[TestMethod]
[UnitTest]
public void NameProperlySet()
{
Assert.AreEqual(this.Descriptor.Name, this.Sut.Name);
}
[TestMethod]
[UnitTest]
public void DescriptorProperlySet()
{
Assert.AreEqual(this.Descriptor, this.Sut.TableDescriptor);
}
[TestMethod]
[UnitTest]
public void WhenConstructedHasNoRowsOrColumns()
{
Assert.AreEqual(0, this.Sut.RowCount);
Assert.AreEqual(0, this.Sut.Columns.Count());
}
[TestMethod]
[UnitTest]
public void SetRowCountSets()
{
this.Sut.SetRowCount(23);
Assert.AreEqual(23, this.Sut.RowCount);
}
[TestMethod]
[UnitTest]
public void SetRowCountReturnsBuilder()
{
Assert.AreEqual(this.Sut, this.Sut.SetRowCount(23));
}
[TestMethod]
[UnitTest]
public void SetRowCountDoesNotAllowNegativeNumber()
{
Assert.ThrowsException<ArgumentOutOfRangeException>(
() => this.Sut.SetRowCount(-1));
}
[TestMethod]
[UnitTest]
public void AddColumnDoesNotAllowNulls()
{
Assert.ThrowsException<ArgumentNullException>(
() => this.Sut.AddColumn(null));
}
[TestMethod]
[UnitTest]
public void AddColumnAdds()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 100, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.AddColumn(column);
Assert.IsTrue(this.Sut.Columns.Contains(column));
}
[TestMethod]
[UnitTest]
public void AddColumnReturnsBuilder()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 100, },
Projection.CreateUsingFuncAdaptor(i => "test"));
Assert.AreEqual(this.Sut, this.Sut.AddColumn(column));
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Performance.SDK.Runtime.NetCoreApp\Microsoft.Performance.SDK.Runtime.NetCoreApp.csproj" />
<ProjectReference Include="..\Microsoft.Performance.SDK.Runtime\Microsoft.Performance.SDK.Runtime.csproj" />
<ProjectReference Include="..\Microsoft.Performance.SDK\Microsoft.Performance.SDK.csproj" />
<ProjectReference Include="..\Microsoft.Performance.Testing\Microsoft.Performance.Testing.csproj" />
<ProjectReference Include="..\Microsoft.Performance.Testing.SDK\Microsoft.Performance.Testing.SDK.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

66
Microsoft.Performance.SDK.Runtime.Tests/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.Performance.SDK.Runtime.Tests.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Performance.SDK.Runtime.Tests.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

Просмотреть файл

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

Просмотреть файл

@ -0,0 +1,326 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public sealed class TableBuilderTests
{
private TableBuilder Sut { get; set; }
[TestInitialize]
public void Initialize()
{
this.Sut = new TableBuilder();
}
[TestMethod]
[UnitTest]
public void WhenConstructedHasNoRowsOrColumns()
{
Assert.AreEqual(0, this.Sut.RowCount);
Assert.AreEqual(0, this.Sut.Columns.Count());
}
[TestMethod]
[UnitTest]
public void SetRowCountSets()
{
this.Sut.SetRowCount(23);
Assert.AreEqual(23, this.Sut.RowCount);
}
[TestMethod]
[UnitTest]
public void SetRowCountReturnsBuilder()
{
Assert.AreEqual(this.Sut, this.Sut.SetRowCount(23));
}
[TestMethod]
[UnitTest]
public void SetRowCountDoesNotAllowNegativeNumber()
{
Assert.ThrowsException<ArgumentOutOfRangeException>(
() => this.Sut.SetRowCount(-1));
}
[TestMethod]
[UnitTest]
public void AddColumnDoesNotAllowNulls()
{
Assert.ThrowsException<ArgumentNullException>(
() => this.Sut.AddColumn(null));
}
[TestMethod]
[UnitTest]
public void AddColumnAdds()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.AddColumn(column);
Assert.IsTrue(this.Sut.Columns.Contains(column));
}
[TestMethod]
[UnitTest]
public void AddColumnReturnsBuilder()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
Assert.AreEqual(this.Sut, this.Sut.AddColumn(column));
}
[TestMethod]
[UnitTest]
public void ReplaceColumnOldNullThrows()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
Assert.ThrowsException<ArgumentNullException>(
() => this.Sut.ReplaceColumn(null, column));
}
[TestMethod]
[UnitTest]
public void ReplaceColumnNewNullThrows()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
Assert.ThrowsException<ArgumentNullException>(
() => this.Sut.ReplaceColumn(column, null));
}
[TestMethod]
[UnitTest]
public void ReplaceColumnWithSelfNoOps()
{
var column = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.AddColumn(column);
this.Sut.ReplaceColumn(column, column);
Assert.AreEqual(1, this.Sut.Columns.Count);
Assert.AreEqual(column, this.Sut.Columns.Single());
}
[TestMethod]
[UnitTest]
public void ReplaceColumnThatExistsReplaces()
{
var columnOld = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
var columnNew = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.AddColumn(columnOld);
this.Sut.ReplaceColumn(columnOld, columnNew);
Assert.AreEqual(1, this.Sut.Columns.Count);
Assert.AreEqual(columnNew, this.Sut.Columns.Single());
}
[TestMethod]
[UnitTest]
public void ReplaceColumnThatDoesNotExistAdds()
{
var columnNotThere = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
var columnNew = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.ReplaceColumn(columnNotThere, columnNew);
Assert.AreEqual(1, this.Sut.Columns.Count);
Assert.AreEqual(columnNew, this.Sut.Columns.Single());
}
[TestMethod]
[UnitTest]
public void ReplaceColumnReturnsBuilder()
{
var columnOld = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
var columnNew = new BaseDataColumn<string>(
new ColumnMetadata(Guid.NewGuid(), "name"),
new UIHints { Width = 200, },
Projection.CreateUsingFuncAdaptor(i => "test"));
this.Sut.AddColumn(columnOld);
var builder = this.Sut.ReplaceColumn(columnOld, columnNew);
Assert.AreEqual(this.Sut, builder);
}
[TestMethod]
[UnitTest]
public void AddTableCommandInvalidArgumentsThrow()
{
Assert.ThrowsException<ArgumentNullException>(() => this.Sut.AddTableCommand(null, _ => { }));
Assert.ThrowsException<ArgumentException>(() => this.Sut.AddTableCommand(string.Empty, _ => { }));
Assert.ThrowsException<ArgumentNullException>(() => this.Sut.AddTableCommand("test", null));
}
[TestMethod]
[UnitTest]
public void AddTableCommandDuplicateNameThrows()
{
this.Sut.AddTableCommand("test", _ => { });
Assert.ThrowsException<InvalidOperationException>(() => this.Sut.AddTableCommand("test", _ => { }));
}
[TestMethod]
[UnitTest]
public void AddTableCommandDuplicateNameThrowsIrrespectiveOfCase()
{
this.Sut.AddTableCommand("test", _ => { });
Assert.ThrowsException<InvalidOperationException>(() => this.Sut.AddTableCommand("tEsT", _ => { }));
}
[TestMethod]
[UnitTest]
public void AddTableCommandDuplicateNameThrowsIrrespectiveOfPadding()
{
this.Sut.AddTableCommand("test", _ => { });
Assert.ThrowsException<InvalidOperationException>(() => this.Sut.AddTableCommand(" test\t ", _ => { }));
}
[TestMethod]
[UnitTest]
public void AddTableCommandAdds()
{
var name = "test";
IReadOnlyList<int> capturedRows = null;
void callback(IReadOnlyList<int> selectedRows)
{
capturedRows = selectedRows;
}
this.Sut.AddTableCommand(name, callback);
Assert.AreEqual(1, this.Sut.Commands.Count);
var command = this.Sut.Commands.Single();
Assert.IsNotNull(command);
Assert.AreEqual(name, command.MenuName);
var testList = new List<int> { 1, 2, 3, }.AsReadOnly();
command.Callback(testList);
Assert.IsNotNull(capturedRows);
Assert.AreEqual(testList, capturedRows);
}
[TestMethod]
[UnitTest]
public void AddTableCommandAddsWithTrimmedName()
{
var name = " test\r \t\n ";
var expectedName = name.Trim();
IReadOnlyList<int> capturedRows = null;
void callback(IReadOnlyList<int> selectedRows)
{
capturedRows = selectedRows;
}
this.Sut.AddTableCommand(name, callback);
Assert.AreEqual(1, this.Sut.Commands.Count);
var command = this.Sut.Commands.Single();
Assert.IsNotNull(command);
Assert.AreEqual(expectedName, command.MenuName);
var testList = new List<int> { 1, 2, 3, }.AsReadOnly();
command.Callback(testList);
Assert.IsNotNull(capturedRows);
Assert.AreEqual(testList, capturedRows);
}
[TestMethod]
[UnitTest]
public void AddTableCommandReturnsInstanceOfBuilder()
{
var ret = this.Sut.AddTableCommand("test", _ => { });
Assert.AreEqual(this.Sut, ret);
}
[TestMethod]
[UnitTest]
public void AddTableCommandMultipleCommandsCanBeAdded()
{
var name1 = "test1";
var name2 = "test2";
var name3 = "test3";
IReadOnlyList<int> captured1 = null;
IReadOnlyList<int> captured2 = null;
IReadOnlyList<int> captured3 = null;
this.Sut.AddTableCommand(name1, x => captured1 = x);
this.Sut.AddTableCommand(name2, x => captured2 = x);
this.Sut.AddTableCommand(name3, x => captured3 = x);
Assert.AreEqual(3, this.Sut.Commands.Count);
var command1 = this.Sut.Commands.SingleOrDefault(x => x.MenuName == name1);
Assert.IsNotNull(command1);
Assert.AreEqual(name1, command1.MenuName);
var test1 = new List<int> { 1, 2, 3, }.AsReadOnly();
command1.Callback(test1);
Assert.AreEqual(captured1, test1);
var command2 = this.Sut.Commands.SingleOrDefault(x => x.MenuName == name2);
Assert.IsNotNull(command2);
Assert.AreEqual(name2, command2.MenuName);
var test2 = new List<int> { 1, 2, 3, }.AsReadOnly();
command2.Callback(test2);
Assert.AreEqual(captured2, test2);
var command3 = this.Sut.Commands.SingleOrDefault(x => x.MenuName == name3);
Assert.IsNotNull(command3);
Assert.AreEqual(name3, command3.MenuName);
var test3 = new List<int> { 1, 2, 3, }.AsReadOnly();
command3.Callback(test3);
Assert.AreEqual(captured3, test3);
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.DTO;
using Microsoft.Performance.Testing;
using Microsoft.Performance.Testing.SDK;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TableConfiguration = Microsoft.Performance.SDK.Processing.TableConfiguration;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[TestClass]
public class TableConfigurationSerializationTests
{
private Guid TableGuid { get; set; }
private TableConfigurationsSerializer Sut { get; set; }
[TestInitialize]
public void Setup()
{
this.TableGuid = Guid.NewGuid();
this.Sut = new TableConfigurationsSerializer();
}
[TestMethod]
[UnitTest]
public void ColumnRoles_EmptySerializes()
{
var c1 = Any.ColumnConfiguration();
var c2 = Any.ColumnConfiguration();
var c3 = Any.ColumnConfiguration();
var c4 = Any.ColumnConfiguration();
var c5 = Any.ColumnConfiguration();
var config = new TableConfiguration("test")
{
Columns = new[] { c1, c2, c3, c4, c5, },
};
using (var stream = new MemoryStream())
{
TableConfigurationsSerializer.SerializeTableConfiguration(stream, config, this.TableGuid);
stream.Seek(0, SeekOrigin.Begin);
var roundTripped = this.Sut.DeserializeTableConfigurations(stream).ToList();
Assert.AreEqual(1, roundTripped.Count);
Assert.IsNotNull(roundTripped[0].Configurations);
var configurations = roundTripped[0].Configurations.ToList();
Assert.AreEqual(1, configurations.Count);
var roundTrippedConfig = configurations[0];
Assert.IsNotNull(roundTrippedConfig);
Assert.AreEqual(0, roundTrippedConfig.ColumnRoles.Count);
}
}
[TestMethod]
[UnitTest]
public void ColumnRoles_ValidValuesSerialize()
{
var config = new TableConfiguration("test");
var roles = Enum.GetValues(typeof(ColumnRole))
.Cast<ColumnRole>()
.Where(x => x.IsValidColumnRole())
.ToList();
var columns = Enumerable.Range(0, roles.Count).Select(_ => Any.ColumnConfiguration()).ToList();
config.Columns = columns;
for (var i = 0; i < roles.Count; ++i)
{
var role = roles[i];
var column = columns[i];
config.AddColumnRole(role, column.Metadata.Guid);
}
using (var stream = new MemoryStream())
{
TableConfigurationsSerializer.SerializeTableConfiguration(stream, config, this.TableGuid);
stream.Seek(0, SeekOrigin.Begin);
var roundTripped = this.Sut.DeserializeTableConfigurations(stream).ToList();
Assert.AreEqual(1, roundTripped.Count);
Assert.IsNotNull(roundTripped[0].Configurations);
var configurations = roundTripped[0].Configurations.ToList();
Assert.AreEqual(1, configurations.Count);
var roundTrippedConfig = configurations[0];
Assert.IsNotNull(roundTrippedConfig);
Assert.AreEqual(config.ColumnRoles.Count, roundTrippedConfig.ColumnRoles.Count);
foreach(var kvp in config.ColumnRoles)
{
Assert.IsTrue(roundTrippedConfig.ColumnRoles.ContainsKey(kvp.Key));
Assert.AreEqual(
kvp.Value,
roundTrippedConfig.ColumnRoles[kvp.Key]);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,488 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
[CustomDataSource("{CABDB99F-F182-457B-B0B4-AD3DD62272D8}", "One", "One")]
[FileDataSource(".csv")]
public sealed class CdsOne
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{CBA22346-53DB-44C7-9039-2CC5FADC07C1}", "Two", "Two")]
[FileDataSource(".xml")]
public sealed class CdsTwo
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{0E031D79-9760-42FA-9E20-B5A957006545}", "Three", "Three")]
[FileDataSource(".json")]
public sealed class CdsThree
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{72028435-50C8-4045-AD46-2CD6304E5BF1}", "Four", "Four")]
[FileDataSource(".json")]
public sealed class CdsWithoutInterface
{
}
[FileDataSource(".json")]
public sealed class CdsWithoutCdsAttribute
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{215A72DF-2FD6-4DA5-9F6E-5BD419EAC357}", "Five", "Five")]
public sealed class CdsWithoutDataSourceAttribute
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
public sealed class CdsWithNoAttributes
: ICustomDataSource
{
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{2F7B5A59-6792-48B1-822C-8D5D5F5C6198}", "Six", "Six")]
[FileDataSource(".json")]
public sealed class CdsWithParameterizedConstructor
: ICustomDataSource
{
public CdsWithParameterizedConstructor(string parameter)
{
}
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{228F130B-CE96-4195-8EE5-54B9B5056F6B}", "Seven", "Seven")]
[FileDataSource(".json")]
public sealed class CdsWithParameterlessAndParameterizedConstructor
: ICustomDataSource
{
public CdsWithParameterlessAndParameterizedConstructor()
{
}
public CdsWithParameterlessAndParameterizedConstructor(string parameter)
{
}
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
[CustomDataSource("{797D49BC-D0EC-4C8D-B64E-CFABE0707CFF}", "Eight", "Eight")]
[FileDataSource(".json")]
public sealed class CdsWithInaccessibleConstructor
: ICustomDataSource
{
internal CdsWithInaccessibleConstructor()
{
}
public IEnumerable<TableDescriptor> DataTables => new TableDescriptor[0];
public IEnumerable<TableDescriptor> MetadataTables => new TableDescriptor[0];
public IEnumerable<Option> CommandLineOptions => new Option[0];
public void SetApplicationEnvironment(IApplicationEnvironment applicationEnvironment)
{
}
public ICustomDataProcessor CreateProcessor(IDataSource dataSource, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public ICustomDataProcessor CreateProcessor(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
throw new NotImplementedException();
}
public Stream GetSerializationStream(SerializationSource source)
{
throw new NotImplementedException();
}
public IEnumerable<TableConfiguration> GetTableConfigurations(TableDescriptor descriptor)
{
throw new NotImplementedException();
}
public CustomDataSourceInfo GetAboutInfo()
{
throw new NotImplementedException();
}
public bool IsFileSupported(string path)
{
throw new NotImplementedException();
}
public void SetLogger(ILogger logger)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace Microsoft.Performance.SDK.Runtime.Tests
{
public class TestProgress
: IProgress<int>
{
public List<int> ReportedValues { get; set; } = new List<int>(100);
public void Report(int value)
{
this.ReportedValues.Add(value);
}
}
}

Просмотреть файл

@ -0,0 +1,198 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers;
using System;
namespace Microsoft.Performance.SDK.Runtime
{
/// <inheritdoc cref="IApplicationEnvironment"/>
public class ApplicationEnvironment
: IApplicationEnvironment
{
private readonly IMessageBox messageBox;
/// <summary>
/// Creates an instance of the <see cref="ApplicationEnvironment"/> class.
/// </summary>
/// <param name="applicationName">
/// Name of the application.
/// </param>
/// <param name="runtimeName">
/// Name of the runtime on which the application is built.
/// </param>
/// <param name="tableDataSynchronizer">
/// Used to synchronize table data changes with the user interface.
/// </param>
/// <param name="serializer">
/// Used to serialize/deserialize data (e.g. table configurations).
/// </param>
/// <param name="dataCookers">
/// A repository of source data cookers.
/// </param>
/// <param name="sourceSessionFactory">
/// Provides a way to create an <see cref="ISourceProcessingSession{T, TKey, TContext}"/> for a data processor.
/// </param>
/// <param name="messageBox">
/// Provides a way to message the user.
/// </param>
public ApplicationEnvironment(
string applicationName,
string runtimeName,
ITableDataSynchronization tableDataSynchronizer,
ISerializer serializer,
ISourceDataCookerRepository dataCookers,
ISourceSessionFactory sourceSessionFactory,
IMessageBox messageBox)
{
// application and runtime names may be null
Guard.NotNull(tableDataSynchronizer, nameof(tableDataSynchronizer));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(dataCookers, nameof(dataCookers));
Guard.NotNull(sourceSessionFactory, nameof(sourceSessionFactory));
Guard.NotNull(messageBox, nameof(messageBox));
this.ApplicationName = applicationName ?? string.Empty;
this.RuntimeName = runtimeName ?? string.Empty;
this.messageBox = messageBox;
// _CDS_
// todo:when tests are ready, consider checking that columnController is not null
this.Serializer = serializer;
this.TableDataSynchronizer = tableDataSynchronizer;
this.SourceDataCookerFactoryRetrieval = dataCookers;
this.SourceSessionFactory = sourceSessionFactory;
}
/// <summary>
/// Gets the name of the application.
/// </summary>
public string ApplicationName { get; }
/// <summary>
/// Gets the name of the runtime on which the application is built, if any.
/// </summary>
public string RuntimeName { get; }
/// <summary>
/// Gets a value that indicates whether the process is running in a graphical user environment.
/// </summary>
public bool GraphicalUserEnvironment { get; set; }
/// <summary>
/// Gets an object to serialize/deserialize data (e.g. table configurations).
/// </summary>
public ISerializer Serializer { get; }
/// <summary>
/// Gets an object used to synchronize table data changes with the user interface.
/// </summary>
public ITableDataSynchronization TableDataSynchronizer { get; }
/// <summary>
/// Gets a value that indicates if verbose output is enabled.
/// </summary>
public bool VerboseOutput { get; set; }
/// <summary>
/// Gets an object that is used to retrieve an <see cref="ISourceDataCookerFactory"/>.
/// </summary>
public ISourceDataCookerFactoryRetrieval SourceDataCookerFactoryRetrieval { get; }
/// <summary>
/// Gets an object that is used to create an <see cref="ISourceProcessingSession{T, TKey, TContext}"/>
/// for a data processor.
/// </summary>
public ISourceSessionFactory SourceSessionFactory { get; }
/// <summary>
/// Displays a message using an <see cref="IMessageBox"/>.
/// </summary>
/// <param name="messageType">
/// The type of message to display.
/// </param>
/// <param name="formatProvider">
/// Provides a mechanism for retrieving an object to control message formatting.
/// </param>
/// <param name="format">
/// A composite format string.
/// </param>
/// <param name="args">
/// An object array that contains zero or more objects to format.
/// </param>
public void DisplayMessage(
MessageType messageType,
IFormatProvider formatProvider,
string format,
params object[] args)
{
this.messageBox.Show(
MapToImage(messageType),
formatProvider,
format,
args);
}
/// <summary>
/// Displays a message using an <see cref="IMessageBox"/>.
/// </summary>
/// <param name="messageType">
/// The type of message to display.
/// </param>
/// <param name="formatProvider">
/// Provides a mechanism for retrieving an object to control message formatting.
/// </param>
/// <param name="buttons">
/// A value that specifies which button or buttons to display.
/// </param>
/// <param name="caption">
/// A <c>string</c> that specifies the title bar caption to display.
/// </param>
/// <param name="format">
/// A composite format string.
/// </param>
/// <param name="args">
/// An object array that contains zero or more objects to format.
/// </param>
public ButtonResult MessageBox(
MessageType messageType,
IFormatProvider formatProvider,
Buttons buttons,
string caption,
string format,
params object[] args)
{
return this.messageBox.Show(
MapToImage(messageType),
formatProvider,
buttons,
caption,
format,
args);
}
private static MessageBoxIcon MapToImage(MessageType messageType)
{
switch (messageType)
{
case MessageType.Error:
return MessageBoxIcon.Error;
case MessageType.Warning:
return MessageBoxIcon.Warning;
case MessageType.Information:
// this is the default. Fall through
default:
return MessageBoxIcon.Information;
}
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Encapsulates functionality used to extend the Assembly implementation.
/// </summary>
public static class AssemblyExtensions
{
/// <summary>
/// Gets a local path to the given <see cref="Assembly"/>.
/// </summary>
/// <param name="self">
/// The target <see cref="Assembly"/>.
/// </param>
/// <returns>
/// The local operating-system representation of a file path to the <see cref="Assembly"/>.
/// </returns>
public static string GetCodeBaseAsLocalPath(this Assembly self)
{
Guard.NotNull(self, nameof(self));
var codeBaseUri = new Uri(self.CodeBase);
return codeBaseUri.LocalPath;
}
}
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Abstract class meant to be extended to support
/// functionality like data extensibility and custom data sources.
/// </summary>
/// <typeparam name="TDerived">
/// A type that extends this class.
/// </typeparam>
public abstract class AssemblyTypeReference<TDerived>
: IEquatable<TDerived>,
ICloneable<TDerived>
where TDerived : AssemblyTypeReference<TDerived>
{
/// <summary>
/// Initializes a new instance of <see cref="AssemblyTypeReference{TDerived}"/>.
/// </summary>
/// <param name="type">
/// Reference <see cref="Type"/>
/// </param>
protected AssemblyTypeReference(Type type)
{
Debug.Assert(type != null);
this.Type = type;
if (!this.Type.Assembly.IsDynamic)
{
this.AssemblyPath = this.Type.Assembly.Location;
this.Version = FileVersionInfo.GetVersionInfo(this.AssemblyPath).FileVersion;
}
else
{
this.AssemblyPath = this.Type.FullName;
this.Version = this.Type.FullName;
}
}
/// <summary>
/// Initializes a new instance of <see cref="AssemblyTypeReference{TDerived}"/> using
/// <paramref name="other"/>'s <see cref="AssemblyTypeReference{TDerived}.Type"/>,
/// <see cref="AssemblyTypeReference{TDerived}.AssemblyPath"/>, and
/// <see cref="AssemblyTypeReference{TDerived}.Version"/>.
/// </summary>
/// <param name="other">
/// Instance of <see cref="AssemblyTypeReferenceWithInstance{T, TDerived}"/> to take a reference(s).
/// </param>
protected AssemblyTypeReference(AssemblyTypeReference<TDerived> other)
{
Debug.Assert(other != null);
this.Type = other.Type;
this.AssemblyPath = other.AssemblyPath;
this.Version = other.Version;
}
/// <summary>
/// Gets the assembly <see cref="Type"/> referenced.
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the file path location of the assembly the <see cref="AssemblyTypeReference{TDerived}.Type"/> is located.
/// </summary>
public string AssemblyPath { get; }
/// <summary>
/// Gets the <see cref="FileVersionInfo.FileVersion"/> of the assembly the <see cref="AssemblyTypeReference{TDerived}.Type"/>.
/// </summary>
public string Version { get; }
/// <summary>
/// Checks to see if the <paramref name="candidateType"/> is <see cref="Type.IsPublic"/> and
/// <see cref="SDK.TypeExtensions.IsInstantiatable(Type)"/> in the assembly reference.
/// </summary>
/// <param name="candidateType">
/// <see cref="Type"/> to be checked.
/// </param>
/// <param name="requiredImplementation">
/// <see cref="Type"/> that is implemented by <paramref name="candidateType"/>.
/// </param>
/// <returns>
/// <c>true</c> if <paramref name="candidateType"/> is Public, Instantiatable, and
/// <paramref name="candidateType"/> implements <paramref name="requiredImplementation"/>;
/// <c>false</c> otherwise.
/// </returns>
protected static bool IsValidType(Type candidateType, Type requiredImplementation)
{
Guard.NotNull(candidateType, nameof(candidateType));
Guard.NotNull(requiredImplementation, nameof(requiredImplementation));
if (candidateType.IsPublic() &&
candidateType.IsInstantiatable())
{
if (candidateType.Implements(requiredImplementation))
{
return true;
}
}
return false;
}
/// <summary>
/// Determines whether this <see cref="TDerived"/> and a specified <see cref="TDerived"/> object have the
/// same <see cref="Type"/>, <see cref="AssemblyPath"/>, and <see cref="Version"/>.
/// </summary>
/// <param name="other">
/// The <see cref="TDerived"/> to compare to this instance.
/// </param>
/// <returns>
/// <inheritdoc cref="object.Equals(object)"/>
/// </returns>
public virtual bool Equals(TDerived other)
{
var toCompare = other as AssemblyTypeReference<TDerived>;
return !ReferenceEquals(toCompare, null) &&
this.Type.Equals(toCompare.Type) &&
this.AssemblyPath.Equals(toCompare.AssemblyPath, StringComparison.InvariantCulture) &&
this.Version.Equals(toCompare.Version, StringComparison.InvariantCulture);
}
/// <inheritdoc cref="object.Equals(object)"/>
public override bool Equals(object obj)
{
return this.Equals(obj as TDerived);
}
/// <summary>
/// Creates a clone of the derived type, <typeparamref name="TDerived"/>.
/// </summary>
/// <returns>
/// A clone of the derived object.
/// </returns>
public abstract TDerived CloneT();
/// <inheritdoc cref="ICloneable.Clone()"/>
object ICloneable.Clone()
{
return this.CloneT();
}
/// <inheritdoc cref="object.GetHashCode()"/>
public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = ((hash << 5) + hash) ^ this.Type.GetHashCode();
hash = ((hash << 5) + hash) ^ this.AssemblyPath.GetHashCode();
hash = ((hash << 5) + hash) ^ this.Version.GetHashCode();
return hash;
}
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Abstract class meant to be extended to support
/// functionality like data extensibility and custom data sources,
/// adding an Instance property of type <see cref="T"/>.
/// </summary>
/// <typeparam name="T">
/// Instance type.
/// </typeparam>
/// <typeparam name="Derived">
/// The class that derives from this class.
/// </typeparam>
public abstract class AssemblyTypeReferenceWithInstance<T, Derived>
: AssemblyTypeReference<Derived>
where Derived : AssemblyTypeReferenceWithInstance<T, Derived>
{
/// <summary>
/// Initializes an instance of <see cref="AssemblyTypeReferenceWithInstance{T, Derived}"/> with a new instance of <see cref="T"/>.
/// </summary>
/// <param name="type">
/// <see cref="Type"/> of <see cref="T"/>
/// </param>
protected AssemblyTypeReferenceWithInstance(Type type)
: base(type)
{
this.Instance = (T)Activator.CreateInstance(type);
Debug.Assert(this.Instance != null);
}
/// <summary>
/// Initializes an instance of <see cref="AssemblyTypeReferenceWithInstance{T, Derived}"/> taking a reference to <see cref="AssemblyTypeReferenceWithInstance{T, Derived}.Instance"/> in <paramref name="other"/>.
/// </summary>
/// <param name="other"><inheritdoc/></param>
protected AssemblyTypeReferenceWithInstance(
AssemblyTypeReferenceWithInstance<T, Derived> other)
: base(other)
{
this.Instance = other.Instance;
}
/// <summary>
/// Gets an instance of <see cref="T"/>
/// </summary>
public T Instance { get; }
/// <summary>
/// <inheritdoc cref="AssemblyTypeReference{Derived}.IsValidType(Type, Type)"/>
/// </summary>
/// <param name="candidateType"><see cref="Type"/> to be checked.</param>
/// <returns><inheritdoc cref="AssemblyTypeReference{Derived}.IsValidType(Type, Type)"/></returns>
protected static bool IsValidType(Type candidateType)
{
Guard.NotNull(candidateType, nameof(candidateType));
return IsValidType(candidateType, typeof(T));
}
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Utility helper class for Common Language Infrastructure (CLI) operations
/// </summary>
public static class CliUtils
{
/// <summary>
/// Detects if the assembly is a Common Language Infrastructure (CLI) binary.
/// For details, see <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/assemblies-gac/how-to-determine-if-a-file-is-an-assembly">MSDN</a>.
/// </summary>
/// <param name="fullPathToCandidateDll">
/// Full file path of the assembly to evaluate.
/// </param>
/// <returns>
/// <c>true</c> if the binary is a Common Language Infrastructure (CLI) binary;
/// <c>false</c> otherwise.
/// </returns>
public static bool IsCliAssembly(
string fullPathToCandidateDll)
{
if (fullPathToCandidateDll == null)
{
throw new ArgumentNullException(nameof(fullPathToCandidateDll));
}
bool isCliAssembly;
try
{
AssemblyName.GetAssemblyName(fullPathToCandidateDll);
isCliAssembly = true;
}
// let FileNotFound, SecurityException, and ArgumentException out
catch (BadImageFormatException)
{
isCliAssembly = false;
}
catch (FileLoadException)
{
// the assembly is already loaded, so it must be an CLI assembly.
// we only care if the file is an assembly; we are not actually
// loading the assembly.
isCliAssembly = true;
}
return isCliAssembly;
}
}
}

Просмотреть файл

@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Concurrent version of <see cref="HashSet{T}"/>.
/// </summary>
/// <typeparam name="T">
/// <inheritdoc cref="HashSet{T}"/>
/// </typeparam>
public sealed class ConcurrentSet<T>
: ICollection<T>
{
private ConcurrentDictionary<T, object> items;
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentSet{T}"/> class
/// that is empty and uses the default equality comparer for the set type.
/// </summary>
public ConcurrentSet()
{
this.items = new ConcurrentDictionary<T, object>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentSet{T}"/> class
/// that uses the default equality comparer for the set type, contains elements copied
/// from the specified collection, and has sufficient capacity to accommodate the
/// number of elements copied.
/// </summary>
/// <param name="items">
/// The collection whose elements are copied to the new set.
/// </param>
public ConcurrentSet(IEnumerable<T> items)
{
this.items = new ConcurrentDictionary<T, object>();
foreach (T item in items)
{
Add(item);
}
}
/// <inheritdoc cref="HashSet{T}.Add(T)"/>
public bool Add(T item)
{
return this.items.TryAdd(item, null);
}
void ICollection<T>.Add(T item)
{
Add(item);
}
/// <summary>
/// Removes all elements from the set.
/// </summary>
public void Clear()
{
this.items.Clear();
}
/// <inheritdoc cref="HashSet{T}.Contains(T)"/>
public bool Contains(T item)
{
return this.items.ContainsKey(item);
}
/// <inheritdoc cref="HashSet{T}.CopyTo(T[], int)"/>
public void CopyTo(T[] array, int arrayIndex)
{
this.items.Keys.CopyTo(array, arrayIndex);
}
/// <inheritdoc cref="HashSet{T}.Count"/>
public int Count
{
get
{
return this.items.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
/// <summary>
/// Removes the specified element from the set.
/// </summary>
/// <param name="item">
/// <inheritdoc cref="HashSet{T}.Remove(T)"/>
/// </param>
/// <returns>
/// <inheritdoc cref="HashSet{T}.Remove(T)"/>
/// </returns>
public bool Remove(T item)
{
object value;
return this.items.TryRemove(item, out value);
}
/// <summary>
/// Creates an array of the current set.
/// </summary>
/// <returns>
/// An array that contains the elements from the input sequence.
/// </returns>
public T[] ToArray()
{
// ConcurrentDictionary implements ToArray() in a properly atomic manner, so we don't need locks or any other wizardry
KeyValuePair<T, object>[] kvpArray = this.items.ToArray();
T[] array = new T[kvpArray.Length];
for (int i = 0; i < kvpArray.Length; ++i)
{
array[i] = kvpArray[i].Key;
}
return array;
}
public IEnumerator<T> GetEnumerator()
{
return this.items.Keys.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Utility helper class for creating <see cref="ILogger"/> instances.
/// </summary>
public static class ConsoleLogger
{
/// <summary>
/// Initializes a new instance of <see cref="StreamLogger"/> : <see cref="ILogger"/> that logs to <see cref="Console.Error"/> for <see cref="Type"/> of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">
/// <see cref="Type"/> used to create the <see cref="StreamLogger"/>.
/// </typeparam>
/// <returns>
/// <inheritdoc cref="ConsoleLogger.Create(Type)"/>
/// </returns>
public static ILogger Create<T>()
{
return Create(typeof(T));
}
/// <summary>
/// Initializes a new instance of <see cref="StreamLogger"/> : <see cref="ILogger"/> that logs to <see cref="Console.Error"/> for a particular <see cref="Type"/>.
/// </summary>
/// <param name="type">
/// <see cref="Type"/> used to create the <see cref="StreamLogger"/>.
/// </param>
/// <returns>
/// Returns a instance of <see cref="ILogger"/>.
/// </returns>
public static ILogger Create(Type type)
{
Guard.NotNull(type, nameof(type));
return new StreamLogger(type, Console.Error);
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime
{
public static class CustomDataSourceConstants
{
/// <summary>
/// Default Custom Data Source Root Folder for loading plugins.
/// </summary>
public const string CustomDataSourceRootFolderName = "CustomDataSources";
}
}

Просмотреть файл

@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Executes the processor of a data source.
/// </summary>
public sealed class CustomDataSourceExecutor
{
private List<TableDescriptor> enabledTables;
private Dictionary<TableDescriptor, Exception> failedToEnableTables;
/// <summary>
/// The custom data processor created by this object.
/// </summary>
public ICustomDataProcessor Processor { get; private set; }
/// <summary>
/// Provides access to the <see cref="ExecutionContext"/> used to initialize this object.
/// </summary>
public ExecutionContext Context { get; private set; }
/// <summary>
/// Creates the custom data processor and enables the specified tables.
/// </summary>
/// <param name="context">
/// Context about the processor and tables to enable.
/// </param>
public void InitializeCustomDataProcessor(ExecutionContext context)
{
Guard.NotNull(context, nameof(context));
this.Processor = context.CustomDataSource.CreateProcessor(
context.DataSources,
context.ProcessorEnvironment,
context.CommandLineOptions);
if (this.Processor == null)
{
throw new InvalidOperationException("Unable to create the data processor.");
}
this.Context = context;
this.enabledTables = new List<TableDescriptor>();
this.failedToEnableTables = new Dictionary<TableDescriptor, Exception>();
Parallel.ForEach(
context.TablesToEnable,
table =>
{
try
{
this.Processor.EnableTable(table);
lock (this.enabledTables)
{
this.enabledTables.Add(table);
}
}
catch (Exception e)
{
lock (this.failedToEnableTables)
{
this.failedToEnableTables[table] = e;
Console.Error.WriteLine("Unable to enable {0}: {1}", table, e);
}
}
});
}
/// <summary>
/// Runs the custom data processor and builds enabled tables.
/// </summary>
/// <param name="cancellationToken">
/// Cancellation token.
/// </param>
/// <returns>
/// Process execution and table build result context.
/// </returns>
public async Task<ExecutionResult> ExecuteAsync(
CancellationToken cancellationToken)
{
if (this.Processor == null)
{
throw new InvalidOperationException($"{nameof(this.InitializeCustomDataProcessor)} wasn't successfully called before calling this method.");
}
Debug.Assert(this.enabledTables != null, $"{nameof(this.enabledTables)} is somehow null, but the processor was created successfully.");
Debug.Assert(this.failedToEnableTables != null, $"{nameof(this.failedToEnableTables)} is somehow null, but the processor was created successfully.");
try
{
await this.Processor.ProcessAsync(this.Context.ProgressReporter, cancellationToken);
}
catch (Exception e)
{
return new ExecutionResult(this.Context, this.Processor, e);
}
//_CDS_ Should we "delayload" metadata tables?
var metadataTables = new List<MetadataTableBuilder>();
Exception metadataFailure;
try
{
var factory = new MetadataTableBuilderFactory();
this.Processor.BuildMetadataTables(factory);
foreach (var table in factory.CreatedTables)
{
metadataTables.Add(table);
}
metadataFailure = null;
}
catch (Exception e)
{
metadataTables.Clear();
metadataFailure = e;
Console.Error.WriteLine("Unable to build metadata tables for {0}: {1}", this.Processor, e);
}
DataSourceInfo info;
Exception infoFailure;
try
{
info = this.Processor.GetDataSourceInfo() ?? DataSourceInfo.Default;
infoFailure = null;
}
catch (Exception e)
{
info = DataSourceInfo.Default;
infoFailure = e;
Console.Error.WriteLine("Unable to get data source info for {0}: {1}", this.Processor, e);
}
var metadataName = this.Context.CustomDataSource.TryGetName();
return new ExecutionResult(
this.Context,
info,
infoFailure,
this.Processor,
this.failedToEnableTables,
metadataName,
metadataTables,
metadataFailure);
}
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Reflection;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Extension methods for <see cref="ICustomDataSource"/>.
/// </summary>
public static class CustomDataSourceExtensions
{
/// <summary>
/// Extension method to return the supported file extension for a <see cref="ICustomDataSource"/>.
/// </summary>
/// <returns>
/// Returns the file extension; <c>null</c> if not defined.
/// </returns>
public static string TryGetFileExtension(this ICustomDataSource self)
{
var dataSource = self.GetType().GetCustomAttribute<DataSourceAttribute>();
var fileDataSource = dataSource as FileDataSourceAttribute;
return fileDataSource?.FileExtension;
}
/// <summary>
/// Extension method to return the file extension description for a <see cref="ICustomDataSource"/>.
/// </summary>
/// <returns>
/// Returns the file extension description; <c>null</c> if not defined.
/// </returns>
public static string TryGetFileDescription(this ICustomDataSource self)
{
var dataSource = self.GetType().GetCustomAttribute<DataSourceAttribute>();
var fileDataSource = dataSource as FileDataSourceAttribute;
return fileDataSource?.Description;
}
/// <summary>
/// Extension method to return the name for a <see cref="ICustomDataSource"/>.
/// </summary>
/// <returns>
/// Returns the name of the <see cref="ICustomDataSource"/>; <c>null</c> if not defined.
/// </returns>
public static string TryGetName(this ICustomDataSource self)
{
return self.GetType().GetCustomAttribute<CustomDataSourceAttribute>()?.Name;
}
/// <summary>
/// Extension method to return the description for a <see cref="ICustomDataSource"/>.
/// </summary>
/// <returns>
/// Returns the description of the <see cref="ICustomDataSource"/>; <c>null</c> if not defined.
/// </returns>
public static string TryGetDescription(this ICustomDataSource self)
{
return self.GetType().GetCustomAttribute<CustomDataSourceAttribute>()?.Description;
}
/// <summary>
/// Extension method to check if the file is supported by the <see cref="ICustomDataSource"/>.
/// </summary>
/// <param name="filePath">
/// Full path to the file being checked.
/// </param>
/// <returns>
/// <inheritdoc cref="ICustomDataSource.IsFileSupported(string)"/>
/// </returns>
public static bool Supports(this ICustomDataSource self, string filePath)
{
var supportedExtension = FileExtensionUtils.CanonicalizeExtension(self.TryGetFileExtension());
var fileExtension = FileExtensionUtils.CanonicalizeExtension(Path.GetExtension(filePath));
// Should this be invariant culture?
return StringComparer.CurrentCultureIgnoreCase.Equals(fileExtension, supportedExtension) &&
self.IsFileSupported(filePath);
}
/// <summary>
/// Extension method to return the <see cref="Guid"/> for a <see cref="ICustomDataSource"/>.
/// </summary>
/// <returns>
/// Returns the <see cref="Guid"/> of the <see cref="ICustomDataSource"/>; <see cref="Guid.Empty"/> if not defined.
/// </returns>
public static Guid TryGetGuid(this ICustomDataSource self)
{
var dataSourceId = self.GetType().GetCustomAttribute<CustomDataSourceAttribute>()?.Guid;
return dataSourceId.HasValue ? dataSourceId.Value : Guid.Empty;
}
}
}

Просмотреть файл

@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Creates a <see cref="AssemblyTypeReferenceWithInstance{T, Derived}"/> where T is <see cref="ICustomDataSource"/> and Derived is <see cref="CustomDataSourceReference"/>.
/// </summary>
public sealed class CustomDataSourceReference
: AssemblyTypeReferenceWithInstance<ICustomDataSource, CustomDataSourceReference>
{
private static CustomDataSourceReferenceComparer equalityComparer;
private CustomDataSourceReference(
Type type,
CustomDataSourceAttribute metadata,
DataSourceAttribute dataSource)
: base(type)
{
Debug.Assert(metadata != null);
Debug.Assert(dataSource != null);
Debug.Assert(metadata.Equals(type.GetCustomAttribute<CustomDataSourceAttribute>()));
Debug.Assert(dataSource.Equals(type.GetCustomAttribute<DataSourceAttribute>()));
this.Guid = metadata.Guid;
this.Name = metadata.Name;
this.Description = metadata.Description;
this.DataSource = dataSource;
this.CommandLineOptions = this.Instance.CommandLineOptions.ToList().AsReadOnly();
}
private CustomDataSourceReference(
CustomDataSourceReference other)
: base(other)
{
this.Guid = other.Guid;
this.Name = other.Name;
this.Description = other.Description;
this.DataSource = other.DataSource;
this.CommandLineOptions = other.CommandLineOptions;
}
/// <inheritdoc cref="CustomDataSourceAttribute.Guid"/>
public Guid Guid { get; }
/// <inheritdoc cref="CustomDataSourceAttribute.Name"/>
public string Name { get; }
/// <inheritdoc cref="CustomDataSourceAttribute.Description"/>
public string Description { get; }
/// <summary>
/// Gets the <see cref="DataSourceAttribute"/> for the custom data source.
/// </summary>
public DataSourceAttribute DataSource { get; }
/// <inheritdoc cref="ICustomDataSource.DataTables"/>
public IEnumerable<TableDescriptor> AvailableTables => this.Instance.DataTables.ToList().AsReadOnly();
/// <inheritdoc cref="ICustomDataSource.CommandLineOptions"/>
public IEnumerable<Option> CommandLineOptions { get; }
/// <summary>
/// Tries to create a instance of the <see cref="CustomDataSourceReference"/> based on the <paramref name="candidateType"/>
/// </summary>
/// <param name="candidateType">
/// Candidate <see cref="Type"/> for the <see cref="CustomDataSourceReference"/>
/// </param>
/// <param name="reference">
/// Out <see cref="CustomDataSourceReference"/>
/// </param>
/// <returns>
/// <c>true</c> if the <paramref name="candidateType"/> is valid and can create a instance of <see cref="CustomDataSourceReference"/>;
/// <c>false</c> otherwise.
/// </returns>
public static bool TryCreateReference(
Type candidateType,
out CustomDataSourceReference reference)
{
Guard.NotNull(candidateType, nameof(candidateType));
reference = null;
if (IsValidType(candidateType))
{
if (candidateType.TryGetEmptyPublicConstructor(out var constructor))
{
var metadataAttribute = candidateType.GetCustomAttribute<CustomDataSourceAttribute>();
if (metadataAttribute != null)
{
var dataSourceAttribute = candidateType.GetCustomAttribute<DataSourceAttribute>();
if (dataSourceAttribute != null)
{
reference = new CustomDataSourceReference(
candidateType,
metadataAttribute,
dataSourceAttribute);
}
}
}
}
return reference != null;
}
public override string ToString()
{
return $"{this.Name} - {this.Guid} ({this.AssemblyPath})";
}
public override bool Equals(CustomDataSourceReference other)
{
return base.Equals(other) &&
this.Name.Equals(other.Name) &&
this.Guid.Equals(other.Guid) &&
this.Description.Equals(other.Description) &&
this.DataSource.Equals(other.DataSource);
}
public override int GetHashCode()
{
unchecked
{
var hash = base.GetHashCode();
hash = ((hash << 5) + hash) ^ this.Name.GetHashCode();
hash = ((hash << 5) + hash) ^ this.Guid.GetHashCode();
hash = ((hash << 5) + hash) ^ this.Description.GetHashCode();
hash = ((hash << 5) + hash) ^ this.DataSource.GetHashCode();
return hash;
}
}
public override CustomDataSourceReference CloneT()
{
return new CustomDataSourceReference(this);
}
/// <summary>
/// Gets the default <see cref="IEqualityComparer{T}"/> for <see cref="CustomDataSourceReference"/>.
/// </summary>
public static IEqualityComparer<CustomDataSourceReference> EqualityComparer
{
get
{
if (equalityComparer == null)
{
equalityComparer = new CustomDataSourceReferenceComparer();
}
return equalityComparer;
}
}
private sealed class CustomDataSourceReferenceComparer
: IEqualityComparer<CustomDataSourceReference>
{
public bool Equals(CustomDataSourceReference c1, CustomDataSourceReference c2)
{
return c1.Equals(c2);
}
public int GetHashCode(CustomDataSourceReference c)
{
return (c != null) ? c.GetHashCode() : 0;
}
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Extension methods for <see cref="CustomDataSourceReference"/>.
/// </summary>
public static class CustomDataSourceReferenceExtensions
{
/// <summary>
/// Extension method to get the canonical file extension for a <see cref="CustomDataSourceReference"/>.
/// </summary>
/// <returns>
/// <inheritdoc cref="FileExtensionUtils.CanonicalizeExtension(string)"/>
/// </returns>
public static string TryGetCanonicalFileExtension(this CustomDataSourceReference self)
{
var fileDataSource = self.DataSource as FileDataSourceAttribute;
var fileDataSourceExtension = fileDataSource?.FileExtension;
return FileExtensionUtils.CanonicalizeExtension(fileDataSourceExtension);
}
/// <summary>
/// Extension method to get the description for a <see cref="CustomDataSourceReference"/>.
/// </summary>
/// <returns>
/// <inheritdoc cref="CustomDataSourceExtensions.TryGetFileDescription(ICustomDataSource)"/>
/// </returns>
public static string TryGetFileDescription(this CustomDataSourceReference self)
{
var fileDataSource = self.DataSource as FileDataSourceAttribute;
return fileDataSource?.Description;
}
/// <summary>
/// Extension method to check if the file is supported by the <see cref="CustomDataSourceReference"/>.
/// </summary>
/// <param name="filePath">
/// <inheritdoc cref="CustomDataSourceExtensions.Supports(ICustomDataSource, string)"/>
/// </param>
/// <returns>
/// <inheritdoc cref="CustomDataSourceExtensions.Supports(ICustomDataSource, string)"/>
/// </returns>
public static bool Supports(
this CustomDataSourceReference self,
string filePath)
{
return self.Instance.Supports(filePath);
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class ColumnConfiguration
{
/// <summary>
/// Metadata describing the column.
/// </summary>
[DataMember]
public ColumnMetadata Metadata { get; set; }
/// <summary>
/// UI hints for displaying the column.
/// </summary>
[DataMember]
public UIHints DisplayHints { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class ColumnMetadata
{
/// <summary>
/// Column identifier.
/// </summary>
[DataMember]
public Guid Guid { get; set; }
/// <summary>
/// Column name.
/// </summary>
[DataMember]
public string Name { get; set; }
/// <summary>
/// Short Description of the column.
/// </summary>
[DataMember]
public string ShortDescription { get; set; }
/// <summary>
/// Description of the column.
/// </summary>
[DataMember]
public string Description { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class ColumnRoleEntry
{
[DataMember(Order = 1)]
public Guid ColumnGuid { get; set; }
[DataMember(Order = 2)]
public string ColumnName { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,604 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Performance.SDK.Runtime.DTO.Enums;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
internal static class DTOExtensions
{
private static Func<Processing.TableConfiguration, IDictionary<ColumnRole, ColumnRoleEntry>> defaultColumnRolesConverter =
(configuration) =>
{
var columnRoles = new Dictionary<ColumnRole, ColumnRoleEntry>();
foreach (var kvp in configuration.ColumnRoles)
{
columnRoles[kvp.Key.ConvertToDto()] = new ColumnRoleEntry()
{
ColumnGuid = kvp.Value,
ColumnName = configuration.Columns.FirstOrDefault(column => column.Metadata.Guid == kvp.Value)?.Metadata.Name,
};
}
return columnRoles;
};
internal static PrebuiltConfigurations ConvertToDto(
this Processing.TableConfigurations tableConfigurations)
{
return tableConfigurations.ConvertToDto(defaultColumnRolesConverter);
}
internal static PrebuiltConfigurations ConvertToDto(
this Processing.TableConfigurations tableConfigurations,
Func<Processing.TableConfiguration, IDictionary<ColumnRole, ColumnRoleEntry>> convertColumnRoles)
{
var dtoTableConfigurations = new PrebuiltConfigurations()
{
Version = PrebuiltConfigurations.DTOVersion,
Tables = new TableConfigurations[]
{
new TableConfigurations()
{
TableId = tableConfigurations.TableId,
Configurations = tableConfigurations.Select(config => config.ConvertToDto(convertColumnRoles)).ToArray(),
DefaultConfigurationName = tableConfigurations.DefaultConfigurationName
}
}
};
return dtoTableConfigurations;
}
internal static Microsoft.Performance.SDK.Processing.TableConfigurations ConvertToSdk(this TableConfigurations dto)
{
var configurations = new List<Microsoft.Performance.SDK.Processing.TableConfiguration>();
if (dto.Configurations != null)
{
foreach (var tableConfiguration in dto.Configurations)
{
configurations.Add(ConvertToSdk(tableConfiguration));
}
}
var tableConfigurations = new Microsoft.Performance.SDK.Processing.TableConfigurations(dto.TableId)
{
Configurations = configurations
};
if (!string.IsNullOrWhiteSpace(dto.DefaultConfigurationName))
{
tableConfigurations.DefaultConfigurationName = dto.DefaultConfigurationName;
}
return tableConfigurations;
}
private static Microsoft.Performance.SDK.Processing.TableConfiguration ConvertToSdk(this TableConfiguration dto)
{
var tableConfiguration = new Microsoft.Performance.SDK.Processing.TableConfiguration(dto.Name)
{
ChartType = dto.ChartType.ConvertToSdk(),
Layout = dto.Layout.ConvertToSdk(),
AggregationOverTime = dto.AggregationOverTime.ConvertToSDK(),
InitialFilterQuery = dto.InitialFilterQuery,
InitialExpansionQuery = dto.InitialExpansionQuery,
InitialSelectionQuery = dto.InitialSelectionQuery,
InitialFilterShouldKeep = dto.InitialFilterShouldKeep,
GraphFilterTopValue = dto.GraphFilterTopValue,
GraphFilterThresholdValue = dto.GraphFilterThresholdValue,
GraphFilterColumnName = dto.GraphFilterColumnName,
GraphFilterColumnGuid = dto.GraphFilterColumnGuid,
HelpText = dto.HelpText,
};
if (dto.HighlightEntries != null)
{
tableConfiguration.HighlightEntries = dto.HighlightEntries.Select(entry => entry.ConvertToSdk()).ToArray();
}
if (dto.Columns != null)
{
tableConfiguration.Columns = dto.Columns.Select(column => column.ConvertToSdk()).ToArray();
}
if (dto.ColumnRoles != null)
{
foreach (var kvp in dto.ColumnRoles)
{
var role = kvp.Key.ConvertToSdk();
if (!role.HasValue)
{
continue;
}
tableConfiguration.AddColumnRole(role.Value, kvp.Value.ColumnGuid);
}
}
return tableConfiguration;
}
internal static TableConfiguration ConvertToDto(
this Microsoft.Performance.SDK.Processing.TableConfiguration configuration,
Func<Processing.TableConfiguration, IDictionary<ColumnRole, ColumnRoleEntry>> convertColumnRoles)
{
var dto = new TableConfiguration
{
ChartType = configuration.ChartType.ConvertToDto(),
Layout = configuration.Layout.ConvertToDto(),
AggregationOverTime = configuration.AggregationOverTime.ConvertToDto(),
Columns = configuration.Columns.Select(column => column.ConvertToDto()).ToArray(),
ColumnRoles = convertColumnRoles(configuration),
Name = configuration.Name,
InitialFilterQuery = configuration.InitialFilterQuery,
InitialExpansionQuery = configuration.InitialExpansionQuery,
InitialSelectionQuery = configuration.InitialSelectionQuery,
InitialFilterShouldKeep = configuration.InitialFilterShouldKeep,
GraphFilterTopValue = configuration.GraphFilterTopValue,
GraphFilterThresholdValue = configuration.GraphFilterThresholdValue,
GraphFilterColumnName = configuration.GraphFilterColumnName,
GraphFilterColumnGuid = configuration.GraphFilterColumnGuid,
HelpText = configuration.HelpText,
HighlightEntries = configuration.HighlightEntries.Select(entry => entry.ConvertToDto()).ToArray(),
};
return dto;
}
private static Microsoft.Performance.SDK.Processing.AggregationMode ConvertToSdk(this AggregationMode dtoEnum)
{
switch (dtoEnum)
{
case AggregationMode.None:
return Microsoft.Performance.SDK.Processing.AggregationMode.None;
case AggregationMode.Average:
return Microsoft.Performance.SDK.Processing.AggregationMode.Average;
case AggregationMode.Sum:
return Microsoft.Performance.SDK.Processing.AggregationMode.Sum;
case AggregationMode.Count:
return Microsoft.Performance.SDK.Processing.AggregationMode.Count;
case AggregationMode.Min:
return Microsoft.Performance.SDK.Processing.AggregationMode.Min;
case AggregationMode.Max:
return Microsoft.Performance.SDK.Processing.AggregationMode.Max;
case AggregationMode.UniqueCount:
return Microsoft.Performance.SDK.Processing.AggregationMode.UniqueCount;
case AggregationMode.Peak:
return Microsoft.Performance.SDK.Processing.AggregationMode.Peak;
case AggregationMode.WeightedAverage:
return Microsoft.Performance.SDK.Processing.AggregationMode.WeightedAverage;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static AggregationMode ConvertToDto(this Microsoft.Performance.SDK.Processing.AggregationMode aggregationMode)
{
switch (aggregationMode)
{
case Microsoft.Performance.SDK.Processing.AggregationMode.None:
return AggregationMode.None;
case Microsoft.Performance.SDK.Processing.AggregationMode.Average:
return AggregationMode.Average;
case Microsoft.Performance.SDK.Processing.AggregationMode.Sum:
return AggregationMode.Sum;
case Microsoft.Performance.SDK.Processing.AggregationMode.Count:
return AggregationMode.Count;
case Microsoft.Performance.SDK.Processing.AggregationMode.Min:
return AggregationMode.Min;
case Microsoft.Performance.SDK.Processing.AggregationMode.Max:
return AggregationMode.Max;
case Microsoft.Performance.SDK.Processing.AggregationMode.UniqueCount:
return AggregationMode.UniqueCount;
case Microsoft.Performance.SDK.Processing.AggregationMode.Peak:
return AggregationMode.Peak;
case Microsoft.Performance.SDK.Processing.AggregationMode.WeightedAverage:
return AggregationMode.WeightedAverage;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Microsoft.Performance.SDK.Processing.ChartType ConvertToSdk(this ChartType dtoEnum)
{
switch (dtoEnum)
{
case ChartType.Line:
return Microsoft.Performance.SDK.Processing.ChartType.Line;
case ChartType.StackedLine:
return Microsoft.Performance.SDK.Processing.ChartType.StackedLine;
case ChartType.StackedBars:
return Microsoft.Performance.SDK.Processing.ChartType.StackedBars;
case ChartType.StateDiagram:
return Microsoft.Performance.SDK.Processing.ChartType.StateDiagram;
case ChartType.PointInTime:
return Microsoft.Performance.SDK.Processing.ChartType.PointInTime;
case ChartType.Flame:
return Microsoft.Performance.SDK.Processing.ChartType.Flame;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static ChartType ConvertToDto(this Microsoft.Performance.SDK.Processing.ChartType chartType)
{
switch (chartType)
{
case Microsoft.Performance.SDK.Processing.ChartType.Line:
return ChartType.Line;
case Microsoft.Performance.SDK.Processing.ChartType.StackedLine:
return ChartType.StackedLine;
case Microsoft.Performance.SDK.Processing.ChartType.StackedBars:
return ChartType.StackedBars;
case Microsoft.Performance.SDK.Processing.ChartType.StateDiagram:
return ChartType.StateDiagram;
case Microsoft.Performance.SDK.Processing.ChartType.PointInTime:
return ChartType.PointInTime;
case Microsoft.Performance.SDK.Processing.ChartType.Flame:
return ChartType.Flame;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Microsoft.Performance.SDK.Processing.TableLayoutStyle ConvertToSdk(this TableLayoutStyle dtoEnum)
{
switch (dtoEnum)
{
case TableLayoutStyle.None:
return Microsoft.Performance.SDK.Processing.TableLayoutStyle.None;
case TableLayoutStyle.Graph:
return Microsoft.Performance.SDK.Processing.TableLayoutStyle.Graph;
case TableLayoutStyle.Table:
return Microsoft.Performance.SDK.Processing.TableLayoutStyle.Table;
case TableLayoutStyle.GraphAndTable:
return Microsoft.Performance.SDK.Processing.TableLayoutStyle.GraphAndTable;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static TableLayoutStyle ConvertToDto(this Microsoft.Performance.SDK.Processing.TableLayoutStyle tableStyleLayout)
{
switch (tableStyleLayout)
{
case Microsoft.Performance.SDK.Processing.TableLayoutStyle.None:
return TableLayoutStyle.None;
case Microsoft.Performance.SDK.Processing.TableLayoutStyle.Graph:
return TableLayoutStyle.Graph;
case Microsoft.Performance.SDK.Processing.TableLayoutStyle.Table:
return TableLayoutStyle.Table;
case Microsoft.Performance.SDK.Processing.TableLayoutStyle.GraphAndTable:
return TableLayoutStyle.GraphAndTable;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Microsoft.Performance.SDK.Processing.AggregationOverTime ConvertToSDK(this AggregationOverTime dtoEnum)
{
switch (dtoEnum)
{
case AggregationOverTime.Current:
return Microsoft.Performance.SDK.Processing.AggregationOverTime.Current;
case AggregationOverTime.Rate:
return Microsoft.Performance.SDK.Processing.AggregationOverTime.Rate;
case AggregationOverTime.Cumulative:
return Microsoft.Performance.SDK.Processing.AggregationOverTime.Cumulative;
case AggregationOverTime.Outstanding:
return Microsoft.Performance.SDK.Processing.AggregationOverTime.Outstanding;
case AggregationOverTime.OutstandingPeak:
return Microsoft.Performance.SDK.Processing.AggregationOverTime.OutstandingPeak;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static AggregationOverTime ConvertToDto(this Microsoft.Performance.SDK.Processing.AggregationOverTime aggregationOverTime)
{
switch (aggregationOverTime)
{
case Microsoft.Performance.SDK.Processing.AggregationOverTime.Current:
return AggregationOverTime.Current;
case Microsoft.Performance.SDK.Processing.AggregationOverTime.Rate:
return AggregationOverTime.Rate;
case Microsoft.Performance.SDK.Processing.AggregationOverTime.Cumulative:
return AggregationOverTime.Cumulative;
case Microsoft.Performance.SDK.Processing.AggregationOverTime.Outstanding:
return AggregationOverTime.Outstanding;
case Microsoft.Performance.SDK.Processing.AggregationOverTime.OutstandingPeak:
return AggregationOverTime.OutstandingPeak;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Microsoft.Performance.SDK.Processing.SortOrder ConvertToSdk(this SortOrder dtoEnum)
{
switch (dtoEnum)
{
case SortOrder.None:
return Microsoft.Performance.SDK.Processing.SortOrder.None;
case SortOrder.Ascending:
return Microsoft.Performance.SDK.Processing.SortOrder.Ascending;
case SortOrder.Descending:
return Microsoft.Performance.SDK.Processing.SortOrder.Descending;
case SortOrder.Ascending_Abs:
return Microsoft.Performance.SDK.Processing.SortOrder.Ascending_Abs;
case SortOrder.Descending_Abs:
return Microsoft.Performance.SDK.Processing.SortOrder.Descending_Abs;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static SortOrder ConvertToDto(this Microsoft.Performance.SDK.Processing.SortOrder sortOrder)
{
switch (sortOrder)
{
case Microsoft.Performance.SDK.Processing.SortOrder.None:
return SortOrder.None;
case Microsoft.Performance.SDK.Processing.SortOrder.Ascending:
return SortOrder.Ascending;
case Microsoft.Performance.SDK.Processing.SortOrder.Descending:
return SortOrder.Descending;
case Microsoft.Performance.SDK.Processing.SortOrder.Ascending_Abs:
return SortOrder.Ascending_Abs;
case Microsoft.Performance.SDK.Processing.SortOrder.Descending_Abs:
return SortOrder.Descending_Abs;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Microsoft.Performance.SDK.Processing.TextAlignment ConvertToSdk(this TextAlignment dtoEnum)
{
switch (dtoEnum)
{
case TextAlignment.Left:
return Microsoft.Performance.SDK.Processing.TextAlignment.Left;
case TextAlignment.Right:
return Microsoft.Performance.SDK.Processing.TextAlignment.Right;
case TextAlignment.Center:
return Microsoft.Performance.SDK.Processing.TextAlignment.Center;
case TextAlignment.Justify:
return Microsoft.Performance.SDK.Processing.TextAlignment.Justify;
default:
//Looks like an unsupported version of the DTO, we shouldn't have got here
throw new InvalidOperationException();
}
}
private static TextAlignment ConvertToDto(this Microsoft.Performance.SDK.Processing.TextAlignment textAlignment)
{
switch (textAlignment)
{
case Microsoft.Performance.SDK.Processing.TextAlignment.Left:
return TextAlignment.Left;
case Microsoft.Performance.SDK.Processing.TextAlignment.Right:
return TextAlignment.Right;
case Microsoft.Performance.SDK.Processing.TextAlignment.Center:
return TextAlignment.Center;
case Microsoft.Performance.SDK.Processing.TextAlignment.Justify:
return TextAlignment.Justify;
default:
// This needs to be updated to support the new enum option.
throw new InvalidOperationException();
}
}
private static Processing.ColumnMetadata ConvertToSdk(this ColumnMetadata dto)
{
return new Processing.ColumnMetadata(dto.Guid, dto.Name, dto.Description) { ShortDescription = dto.ShortDescription };
}
private static ColumnMetadata ConvertToDto(this Processing.ColumnMetadata columnMetadata)
{
var dto = new ColumnMetadata
{
Guid = columnMetadata.Guid,
Name = columnMetadata.Name,
Description = columnMetadata.Description,
ShortDescription = columnMetadata.ShortDescription,
};
return dto;
}
private static Microsoft.Performance.SDK.Processing.UIHints ConvertToSdk(this UIHints dto)
{
var uiHints = new Microsoft.Performance.SDK.Processing.UIHints
{
IsVisible = dto.IsVisible,
TextAlignment = dto.TextAlignment.ConvertToSdk(),
Width = dto.Width,
SortOrder = dto.SortOrder.ConvertToSdk(),
SortPriority = dto.SortPriority,
AggregationMode = dto.AggregationMode.ConvertToSdk(),
CellFormat = dto.CellFormat,
};
return uiHints;
}
private static UIHints ConvertToDto(this Microsoft.Performance.SDK.Processing.UIHints uiHints)
{
var dto = new UIHints
{
IsVisible = uiHints.IsVisible,
TextAlignment = uiHints.TextAlignment.ConvertToDto(),
Width = uiHints.Width,
SortOrder = uiHints.SortOrder.ConvertToDto(),
SortPriority = uiHints.SortPriority,
AggregationMode = uiHints.AggregationMode.ConvertToDto(),
CellFormat = uiHints.CellFormat,
};
return dto;
}
private static Microsoft.Performance.SDK.Processing.ColumnConfiguration ConvertToSdk(
this ColumnConfiguration dto)
{
// These are checked by reference, so they need to point to these instances.
if (dto.Metadata.Guid == Microsoft.Performance.SDK.Processing.TableConfiguration.PivotColumn.Metadata.Guid)
{
return Microsoft.Performance.SDK.Processing.TableConfiguration.PivotColumn;
}
else if (dto.Metadata.Guid == Microsoft.Performance.SDK.Processing.TableConfiguration.LeftFreezeColumn.Metadata.Guid)
{
return Microsoft.Performance.SDK.Processing.TableConfiguration.LeftFreezeColumn;
}
else if (dto.Metadata.Guid == Microsoft.Performance.SDK.Processing.TableConfiguration.RightFreezeColumn.Metadata.Guid)
{
return Microsoft.Performance.SDK.Processing.TableConfiguration.RightFreezeColumn;
}
else if (dto.Metadata.Guid == Microsoft.Performance.SDK.Processing.TableConfiguration.GraphColumn.Metadata.Guid)
{
return Microsoft.Performance.SDK.Processing.TableConfiguration.GraphColumn;
}
var columnConfiguration = new Microsoft.Performance.SDK.Processing.ColumnConfiguration(
dto.Metadata.ConvertToSdk(),
dto.DisplayHints.ConvertToSdk());
return columnConfiguration;
}
private static ColumnConfiguration ConvertToDto(
this Microsoft.Performance.SDK.Processing.ColumnConfiguration columnConfiguration)
{
var dto = new ColumnConfiguration
{
DisplayHints = columnConfiguration.DisplayHints.ConvertToDto(),
Metadata = columnConfiguration.Metadata.ConvertToDto(),
};
return dto;
}
internal static ColumnRole ConvertToDto(this Processing.ColumnRole self)
{
switch (self)
{
case Processing.ColumnRole.StartThreadId:
return ColumnRole.StartThreadId;
case Processing.ColumnRole.EndThreadId:
return ColumnRole.EndThreadId;
case Processing.ColumnRole.StartTime:
return ColumnRole.StartTime;
case Processing.ColumnRole.EndTime:
return ColumnRole.EndTime;
case Processing.ColumnRole.Duration:
return ColumnRole.Duration;
case Processing.ColumnRole.HierarchicalTimeTree:
return ColumnRole.HierarchicalTimeTree;
case Processing.ColumnRole.ResourceId:
return ColumnRole.ResourceId;
case Processing.ColumnRole.WaitDuration:
return ColumnRole.WaitDuration;
case Processing.ColumnRole.WaitEndTime:
return ColumnRole.WaitEndTime;
case Processing.ColumnRole.RecLeft:
return ColumnRole.RecLeft;
case Processing.ColumnRole.RecTop:
return ColumnRole.RecTop;
case Processing.ColumnRole.RecHeight:
return ColumnRole.RecHeight;
case Processing.ColumnRole.RecWidth:
return ColumnRole.RecWidth;
case Processing.ColumnRole.CountColumnMetadata:
return ColumnRole.CountColumnMetadata;
default:
return ColumnRole.Invalid;
}
}
private static Processing.ColumnRole? ConvertToSdk(this ColumnRole dto)
{
switch (dto)
{
case ColumnRole.StartThreadId:
return Processing.ColumnRole.StartThreadId;
case ColumnRole.EndThreadId:
return Processing.ColumnRole.EndThreadId;
case ColumnRole.StartTime:
return Processing.ColumnRole.StartTime;
case ColumnRole.EndTime:
return Processing.ColumnRole.EndTime;
case ColumnRole.Duration:
return Processing.ColumnRole.Duration;
case ColumnRole.HierarchicalTimeTree:
return Processing.ColumnRole.HierarchicalTimeTree;
case ColumnRole.ResourceId:
return Processing.ColumnRole.ResourceId;
case ColumnRole.WaitDuration:
return Processing.ColumnRole.WaitDuration;
case ColumnRole.WaitEndTime:
return Processing.ColumnRole.WaitEndTime;
case ColumnRole.RecLeft:
return Processing.ColumnRole.RecLeft;
case ColumnRole.RecTop:
return Processing.ColumnRole.RecTop;
case ColumnRole.RecHeight:
return Processing.ColumnRole.RecHeight;
case ColumnRole.RecWidth:
return Processing.ColumnRole.RecWidth;
case ColumnRole.CountColumnMetadata:
return Processing.ColumnRole.CountColumnMetadata;
default:
return null;
}
}
private static Microsoft.Performance.SDK.Processing.HighlightEntry ConvertToSdk(
this HighlightEntry dto)
{
var highlightEntry = new Microsoft.Performance.SDK.Processing.HighlightEntry
{
StartTimeColumnName = dto.StartTimeColumnName,
StartTimeColumnGuid = dto.StartTimeColumnGuid,
EndTimeColumnName = dto.EndTimeColumnName,
EndTimeColumnGuid = dto.EndTimeColumnGuid,
DurationColumnName = dto.DurationColumnName,
DurationColumnGuid = dto.EndTimeColumnGuid,
HighlightQuery = dto.HighlightQuery,
HighlightColor = dto.HighlightColor,
};
return highlightEntry;
}
private static HighlightEntry ConvertToDto(
this Microsoft.Performance.SDK.Processing.HighlightEntry highlightEntry)
{
var dto = new HighlightEntry
{
StartTimeColumnName = highlightEntry.StartTimeColumnName,
StartTimeColumnGuid = highlightEntry.StartTimeColumnGuid,
EndTimeColumnName = highlightEntry.EndTimeColumnName,
EndTimeColumnGuid = highlightEntry.EndTimeColumnGuid,
DurationColumnName = highlightEntry.DurationColumnName,
DurationColumnGuid = highlightEntry.EndTimeColumnGuid,
HighlightQuery = highlightEntry.HighlightQuery,
HighlightColor = highlightEntry.HighlightColor,
};
return dto;
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum AggregationMode
{
None,
Average,
Sum,
Count,
Min,
Max,
UniqueCount,
Peak,
WeightedAverage
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum AggregationOverTime
{
Current,
Rate,
Cumulative,
Outstanding,
OutstandingPeak,
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum ChartType
{
Line,
StackedLine,
StackedBars,
StateDiagram,
PointInTime,
Flame
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum ColumnRole
{
Invalid = -1,
StartThreadId = 0,
EndThreadId,
StartTime,
EndTime,
Duration,
HierarchicalTimeTree,
ResourceId,
WaitDuration,
WaitEndTime,
RecLeft,
RecTop,
RecHeight,
RecWidth,
CountColumnMetadata,
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum SortOrder
{
None,
Ascending,
Descending,
Ascending_Abs,
Descending_Abs
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum TableLayoutStyle
{
None = 0x00,
Graph = 0x01,
Table = 0x02,
GraphAndTable = Graph | Table,
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.DTO.Enums
{
internal enum TextAlignment
{
Left,
Right,
Center,
Justify,
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Drawing;
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class HighlightEntry
{
[DataMember(Order = 1)]
public string StartTimeColumnName { get; set; }
[DataMember(Order = 2)]
public Guid StartTimeColumnGuid { get; set; }
[DataMember(Order = 3)]
public string EndTimeColumnName { get; set; }
[DataMember(Order = 4)]
public Guid EndTimeColumnGuid { get; set; }
[DataMember(Order = 5)]
public string DurationColumnName { get; set; }
[DataMember(Order = 6)]
public Guid DurationColumnGuid { get; set; }
[DataMember(Order = 7)]
public string HighlightQuery { get; set; }
[DataMember(Order = 8)]
public Color HighlightColor { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class PrebuiltConfigurations
{
internal static readonly double DTOVersion = 0.1;
public PrebuiltConfigurations()
{
this.Version = DTOVersion;
}
[DataMember(Order = 1)]
public double Version { get; set; }
[DataMember(Order = 2)]
public TableConfigurations[] Tables { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.Performance.SDK.Runtime.DTO.Enums;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class TableConfiguration
{
/// <summary>
/// The table name.
/// </summary>
[DataMember(Order = 1)]
public string Name { get; set; }
/// <summary>
/// The layout style for the table.
/// </summary>
[DataMember(Order = 2)]
public TableLayoutStyle Layout { get; set; }
/// <summary>
/// The type of chart displayed for this table.
/// </summary>
[DataMember(Order = 3)]
public ChartType ChartType { get; set; }
/// <summary>
/// Gets or sets the aggregation over time mode for this table.
/// </summary>
[DataMember(Order = 4)]
public AggregationOverTime AggregationOverTime { get; set; }
/// <summary>
/// Gets or sets the query for initial filtering in this table.
/// </summary>
[DataMember(Order = 5)]
public string InitialFilterQuery { get; set; }
/// <summary>
/// Gets or sets the query for initial expansion in this table.
/// </summary>
[DataMember(Order = 6)]
public string InitialExpansionQuery { get; set; }
/// <summary>
/// Gets or sets the query for initial selection in this table.
/// </summary>
[DataMember(Order = 7)]
public string InitialSelectionQuery { get; set; }
/// <summary>
/// Get or sets whether the initial filter should be kept in this table.
/// </summary>
[DataMember(Order = 8)]
public bool InitialFilterShouldKeep { get; set; }
/// <summary>
/// Gets or sets the top value of the graph filter in this value.
/// </summary>
[DataMember(Order = 9)]
public int GraphFilterTopValue { get; set; }
/// <summary>
/// Gets or sets the threshold value of the graph filter in this table.
/// </summary>
[DataMember(Order = 10)]
public double GraphFilterThresholdValue { get; set; }
/// <summary>
/// Gets or sets the name of the column for graph filtering.
/// </summary>
[DataMember(Order = 11)]
public string GraphFilterColumnName { get; set; }
/// <summary>
/// Gets or sets the ID of the column for graph filtering.
/// </summary>
[DataMember(Order = 12)]
public Guid GraphFilterColumnGuid { get; set; }
/// <summary>
/// Gets or sets an RTF string that is used to show the help information for this table.
/// </summary>
[DataMember(Order = 13)]
public string HelpText { get; set; }
/// <summary>
/// Gets or sets the collection of query entries that are used to highlight in this table.
/// </summary>
[DataMember(Order = 14)]
public IEnumerable<HighlightEntry> HighlightEntries { get; set; }
/// <summary>
/// Columns that may appear in the table.
/// </summary>
[DataMember(Order = 15)]
public IEnumerable<Runtime.DTO.ColumnConfiguration> Columns { get; set; }
/// <summary>
/// The roles and their associated column entries.
/// </summary>
[DataMember(Order = 16)]
public IDictionary<ColumnRole, ColumnRoleEntry> ColumnRoles { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class TableConfigurations
{
[DataMember(Order = 1)]
public Guid TableId { get; set; }
[DataMember(Order = 2)]
public string DefaultConfigurationName { get; set; }
[DataMember(Order = 3)]
public TableConfiguration[] Configurations { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,178 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
/// <summary>
/// Used to de/serialize table configurations from/to a stream.
/// </summary>
public class TableConfigurationsSerializer
: ISerializer
{
private const string indentChars = " ";
/// <inheritdoc/>
public IEnumerable<Processing.TableConfigurations> DeserializeTableConfigurations(Stream stream)
{
return DeserializeTableConfigurations(stream, null);
}
/// <inheritdoc/>
public IEnumerable<Processing.TableConfigurations> DeserializeTableConfigurations(Stream stream, ILogger logger)
{
if (stream == null)
{
return Enumerable.Empty<Processing.TableConfigurations>();
}
var skipByteOrderMark = false;
var startingPosition = stream.Position;
using (var binaryReader = new BinaryReader(stream, Encoding.UTF8, true))
{
var byte1 = binaryReader.ReadByte();
if (byte1 == 0xEF)
{
var byte2 = binaryReader.ReadByte();
if (byte2 == 0xBB)
{
var byte3 = binaryReader.ReadByte();
if (byte3 == 0xBF)
{
skipByteOrderMark = true;
}
}
}
}
if (!skipByteOrderMark)
{
stream.Position = startingPosition;
}
PrebuiltConfigurations prebuiltConfigurations = null;
try
{
var serializer = new DataContractJsonSerializer(typeof(PrebuiltConfigurations));
prebuiltConfigurations = serializer.ReadObject(stream) as PrebuiltConfigurations;
if (prebuiltConfigurations == null)
{
return Enumerable.Empty<Processing.TableConfigurations>();
}
}
catch (InvalidDataContractException e)
{
logger?.Warn($"Invalid {nameof(Processing.TableConfiguration)} data found: {e.Message}");
return Enumerable.Empty<Processing.TableConfigurations>();
}
catch (SerializationException e)
{
logger?.Warn(
$"An exception was encountered while deserializing {nameof(Processing.TableConfiguration)}: " +
$"{e.Message}");
return Enumerable.Empty<Processing.TableConfigurations>();
}
// _CDS_
// todo:validate the version
return ConvertDataTransferObjectsToTableConfigurations(prebuiltConfigurations.Tables);
}
/// <summary>
/// Serializes a table configuration to a stream.
/// </summary>
/// <param name="stream">
/// Target stream.
/// </param>
/// <param name="tableConfiguration">
/// Table configuration to serialize.
/// </param>
/// <param name="tableId">
/// Table identifier.
/// </param>
public static void SerializeTableConfiguration(
Stream stream,
Processing.TableConfiguration tableConfiguration,
Guid tableId)
{
SerializeTableConfiguration(stream, tableConfiguration, tableId, null);
}
/// <summary>
/// Serializes a table configuration to a stream.
/// </summary>
/// <param name="stream">
/// Target stream.
/// </param>
/// <param name="tableConfiguration">
/// Table configuration to serialize.
/// </param>
/// <param name="tableId">
/// Table identifier.
/// </param>
/// <param name="logger">
/// Used to log relevant messages.
/// </param>
public static void SerializeTableConfiguration(
Stream stream,
Processing.TableConfiguration tableConfiguration,
Guid tableId,
ILogger logger)
{
var tableConfigurations = new Processing.TableConfigurations(tableId) { Configurations = new[] { tableConfiguration } };
var prebuiltConfigurations = tableConfigurations.ConvertToDto();
SerializeTableConfigurations(stream, prebuiltConfigurations, logger);
}
private static void SerializeTableConfigurations(
Stream stream,
PrebuiltConfigurations dtoPrebuiltConfigurations,
ILogger logger)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
try
{
using (var writer = JsonReaderWriterFactory.CreateJsonWriter(
stream, Encoding.Default, false, true, indentChars))
{
var serializer = new DataContractJsonSerializer(typeof(PrebuiltConfigurations));
serializer.WriteObject(writer, dtoPrebuiltConfigurations);
}
}
catch (Exception exception)
{
logger?.Warn($"Failed to serialize table configurations: {exception.Message}");
}
}
private static IEnumerable<Processing.TableConfigurations> ConvertDataTransferObjectsToTableConfigurations(
TableConfigurations[] transferObjects)
{
var tableConfigurations = new List<Processing.TableConfigurations>(transferObjects.Length);
foreach (var transferObject in transferObjects)
{
tableConfigurations.Add(transferObject.ConvertToSdk());
}
return tableConfigurations;
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.Serialization;
using Microsoft.Performance.SDK.Runtime.DTO.Enums;
namespace Microsoft.Performance.SDK.Runtime.DTO
{
[DataContract]
internal class UIHints
{
/// <summary>
/// Column width.
/// </summary>
[DataMember]
public int Width { get; set; }
/// <summary>
/// If the column is visible.
/// </summary>
[DataMember]
public bool IsVisible { get; set; }
/// <summary>
/// Text alignment for the column.
/// </summary>
[DataMember]
public TextAlignment TextAlignment { get; set; }
/// <summary>
/// Determines how a column is sorted.
/// </summary>
[DataMember]
public SortOrder SortOrder { get; set; }
/// <summary>
/// Column sort priority.
/// </summary>
[DataMember]
public int SortPriority { get; set; }
/// <summary>
/// This determines how data from the column will be aggregated in the table when
/// multiple rows are collapsed.
/// </summary>
[DataMember]
public AggregationMode AggregationMode { get; set; }
/// <summary>
/// Determines how a value will be displayed in a cell.
/// </summary>
[DataMember]
public string CellFormat { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Handles progress callbacks while processing the data sources.
/// </summary>
public sealed class DataProcessorProgress
: IProgress<int>,
IProgressUpdate
{
/// <summary>
/// Event to be notified when properties change on <see cref="DataProcessorProgress"/>.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private int currentProgress;
public int CurrentProgress
{
get
{
return this.currentProgress;
}
set
{
if (value != this.currentProgress)
{
this.currentProgress = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(CurrentProgressString));
}
}
}
private ProcessorStatus currentStatus;
public ProcessorStatus CurrentStatus
{
get
{
return this.currentStatus;
}
set
{
if (value != this.currentStatus)
{
this.currentStatus = value;
this.RaisePropertyChanged();
}
}
}
public string CurrentProgressString => this.CurrentProgress.ToString() + "%";
public void Report(int progress)
{
this.CurrentProgress = progress;
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Represents a session data source. A session data source
/// is an <see cref="ICustomDataSource"/> and the associated
/// <see cref="IDataSource"/>s that can be processed by the
/// <see cref="ICustomDataSource"/>.
/// </summary>
public sealed class DataSessionSource
{
/// <summary>
/// Initializes a new instance of the <see cref="DataSessionSource"/>
/// class.
/// </summary>
public DataSessionSource(
CustomDataSourceReference customDataSource,
IEnumerable<IDataSource> dataSources,
ProcessorOptions options)
{
Guard.NotNull(customDataSource, nameof(customDataSource));
Guard.NotNull(dataSources, nameof(dataSources));
Guard.NotNull(options, nameof(options));
this.CustomDataSource = customDataSource;
this.DataSources = dataSources.ToList().AsReadOnly();
this.Options = options;
this.ProgressTracker = new DataProcessorProgress();
}
/// <summary>
/// Gets the <see cref="ICustomDataSource"/> associated
/// with the given data items.
/// </summary>
public CustomDataSourceReference CustomDataSource { get; }
/// <summary>
/// Gets the <see cref="IDataSource"/>s that can be
/// processed by the <see cref="CustomDataSource"/>.
/// </summary>
public IEnumerable<IDataSource> DataSources { get; }
/// <summary>
/// Gets the options to pass to the processors.
/// </summary>
public ProcessorOptions Options { get; private set; }
/// <summary>
/// Gets the progress tracker for this instance.
/// </summary>
public DataProcessorProgress ProgressTracker { get; }
/// <summary>
/// Adds an option to this instance.
/// </summary>
/// <param name="newOption">
/// The new option.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="newOption"/> is <c>null</c>.
/// </exception>
public void AddOption(OptionInstance newOption)
{
if (this.Options is null)
{
this.Options = new ProcessorOptions(newOption.AsEnumerableSingleton());
}
else
{
this.Options = new ProcessorOptions(
this.Options.Options.Concat(newOption),
this.Options.Arguments);
}
}
/// <summary>
/// Gets the <see cref="string"/> representation of this
/// instance.
/// </summary>
/// <returns>
/// The <see cref="string"/> representation of this instance.
/// </returns>
public override string ToString()
{
var sb = new StringBuilder();
var dataSourceName = this.CustomDataSource.Name;
sb.Append(dataSourceName);
sb.Append(" (");
var isFirst = true;
foreach (var source in this.DataSources)
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.Append(", ");
}
sb.Append(source.GetUri());
}
sb.Append(")");
return sb.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,260 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// This class loads Assemblies and passes the types found in the assemblies to registered observers to check for
/// possible extensions to the SDK.
/// </summary>
public class AssemblyExtensionDiscovery
: IExtensionTypeProvider
{
private readonly IAssemblyLoader assemblyLoader;
private readonly List<IExtensionTypeObserver> observers = new List<IExtensionTypeObserver>();
/// <summary>
/// Constructor takes an <see cref="IAssemblyLoader"/> which is used to do the actual assembly loading.
/// </summary>
/// <param name="assemblyLoader">
/// This is used to load individual assemblies.
/// </param>
public AssemblyExtensionDiscovery(IAssemblyLoader assemblyLoader)
{
this.assemblyLoader = assemblyLoader;
}
/// <summary>
/// Only unit tests are expected to change this value.
/// </summary>
internal IFindFiles FindFiles { get; set; } = new DirectorySearch();
/// <summary>
/// Called to register to receive notification of loaded types from possible extension modules.
/// </summary>
/// <param name="observer">
/// The object that receives updates of loaded types.
/// </param>
public void RegisterTypeConsumer(IExtensionTypeObserver observer)
{
lock (this.observers)
{
this.observers.Add(observer);
}
}
/// <summary>
/// This method scans the given directory for modules to search for extensibility types. All types found will
/// be passed along to any observers. It searches subdirectories for all "*.dll" and "*.exe" files.
/// </summary>
/// <param name="directoryPath">
/// Directory to process.
/// </param>
public void ProcessAssemblies(string directoryPath)
{
this.ProcessAssemblies(new[] { directoryPath, });
}
/// <summary>
/// This method scans the given directory for modules to search for extensibility types. All types found will
/// be passed along to any observers. It searches subdirectories for all "*.dll" and "*.exe" files.
/// </summary>
/// <param name="directoryPaths">
/// Directories to process.
/// </param>
public void ProcessAssemblies(IEnumerable<string> directoryPaths)
{
this.ProcessAssemblies(directoryPaths, true, null, null, false);
}
/// <summary>
/// This method scans the given directory for modules to search for extensibility types. All types found will
/// be passed along to any observers.
/// </summary>
/// <param name="directoryPath">
/// Directory to process.
/// </param>
/// <param name="includeSubdirectories">
/// Indicates whether subdirectories will be searched.
/// </param>
/// <param name="searchPatterns">
/// The search patterns to use. If null or empty, defaults to "*.dll" and "*.exe".
/// </param>
/// <param name="exclusionFileNames">
/// A set of files names to exclude from the search. May be null.
/// </param>
/// <param name="exclusionsAreCaseSensitive">
/// Indicates whether files names should be treated as case sensitive.
/// </param>
public void ProcessAssemblies(
string directoryPath,
bool includeSubdirectories,
IEnumerable<string> searchPatterns,
IEnumerable<string> exclusionFileNames,
bool exclusionsAreCaseSensitive)
{
this.ProcessAssemblies(
new[] { directoryPath, },
includeSubdirectories,
searchPatterns,
exclusionFileNames,
exclusionsAreCaseSensitive);
}
/// <summary>
/// This method scans the given directory for modules to search for extensibility types. All types found will
/// be passed along to any observers.
/// </summary>
/// <param name="directoryPaths">
/// Directories to process.
/// </param>
/// <param name="includeSubdirectories">
/// Indicates whether subdirectories will be searched.
/// </param>
/// <param name="searchPatterns">
/// The search patterns to use. If null or empty, defaults to "*.dll" and "*.exe".
/// </param>
/// <param name="exclusionFileNames">
/// A set of files names to exclude from the search. May be null.
/// </param>
/// <param name="exclusionsAreCaseSensitive">
/// Indicates whether files names should be treated as case sensitive.
/// </param>
public void ProcessAssemblies(
IEnumerable<string> directoryPaths,
bool includeSubdirectories,
IEnumerable<string> searchPatterns,
IEnumerable<string> exclusionFileNames,
bool exclusionsAreCaseSensitive)
{
Guard.NotNull(directoryPaths, nameof(directoryPaths));
directoryPaths.ForEach(x => Guard.NotNullOrWhiteSpace(x, nameof(directoryPaths)));
var watch = Stopwatch.StartNew();
if (searchPatterns == null || !searchPatterns.Any())
{
searchPatterns = new[] { "*.dll", "*.exe" };
}
var exclusionSet = exclusionFileNames != null
? new HashSet<string>(exclusionFileNames, exclusionsAreCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)
: new HashSet<string>();
var searchOption = includeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var loaded = new List<Assembly>();
lock (this.observers)
{
if (!this.observers.Any())
{
// If a tree falls in a forest and no one is around to hear it, does it make a sound?
return;
}
foreach (var directoryPath in directoryPaths)
{
foreach (var searchPattern in searchPatterns)
{
var filePaths = this.FindFiles.EnumerateFiles(directoryPath, searchPattern, searchOption)
.Where(x => !exclusionSet.Contains(Path.GetFileName(x)))
.ToArray();
if (filePaths.Length == 0)
{
continue;
}
loaded.EnsureCapacity(loaded.Capacity + filePaths.Length);
foreach (var filePath in filePaths)
{
var assembly = this.assemblyLoader.LoadAssembly(filePath);
if (!(assembly is null))
{
ProcessAssembly(assembly);
}
}
}
}
Parallel.ForEach(this.observers, (observer) => observer.DiscoveryComplete());
}
watch.Stop();
Console.Error.WriteLine("Loaded all in {0}", watch.Elapsed);
}
private void ProcessAssembly(Assembly assembly)
{
Guard.NotNull(assembly, nameof(assembly));
var assemblyName = assembly.GetName().FullName;
try
{
foreach (var type in assembly.GetTypes())
{
Parallel.ForEach(observers, (observer) =>
{
observer.ProcessType(type, assemblyName);
});
}
}
catch (ReflectionTypeLoadException e)
{
Console.Error.WriteLine("Unable to examine `{0}`: ", assembly.GetName());
Console.Error.WriteLine("--> {0}", e.Message);
foreach (var loaderException in e.LoaderExceptions)
{
Console.Error.WriteLine("----> {0}", loaderException.Message);
if (loaderException is FileNotFoundException fnfe)
{
Console.Error.WriteLine("------> {0}", fnfe.FusionLog);
}
Console.Error.WriteLine(loaderException.InnerException);
}
}
catch (TargetInvocationException e)
{
Console.Error.WriteLine("Unable to examine `{0}`: ", assembly.GetName());
Console.Error.WriteLine("--> {0}", e.Message);
Console.Error.WriteLine("----> {0}", e.InnerException?.Message ?? "<inner exception was null>");
}
catch (Exception e)
{
Console.Error.WriteLine("Unable to examine `{0}`: ", assembly.GetName());
Console.Error.WriteLine("--> {0}", e.Message);
}
}
/// <summary>
/// This is used to enable unit testing by avoiding the actual file system.
/// </summary>
internal interface IFindFiles
{
IEnumerable<string> EnumerateFiles(string directoryPath, string searchPattern, SearchOption searchOption);
}
/// <summary>
/// This provides the default behavior in all but the unit test case.
/// </summary>
private class DirectorySearch : IFindFiles
{
public IEnumerable<string> EnumerateFiles(string directoryPath, string searchPattern, SearchOption searchOption)
{
return Directory.EnumerateFiles(directoryPath, searchPattern, searchOption);
}
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// Loads assemblies into the default load context.
/// </summary>
public sealed class AssemblyLoader
: IAssemblyLoader
{
/// <inheritdoc />
public bool SupportsIsolation => false;
/// <summary>
/// Loads the specified path as an assembly into the default load context.
/// </summary>
/// <param name="assemblyPath">
/// Path to an assembly.
/// </param>
public Assembly LoadAssembly(string assemblyPath)
{
if (!CliUtils.IsCliAssembly(assemblyPath))
{
return null;
}
Assembly loadedAssembly;
try
{
loadedAssembly = Assembly.LoadFrom(assemblyPath);
}
catch (BadImageFormatException)
{
//
// this means it is native code or otherwise
// not readable by the CLR.
//
loadedAssembly = null;
}
catch (FileLoadException e)
{
Console.Error.WriteLine(
"[warn]: managed assembly `{0}` cannot be loaded - {1}.",
assemblyPath,
e.FusionLog);
loadedAssembly = null;
}
catch (FileNotFoundException)
{
loadedAssembly = null;
}
return loadedAssembly;
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// Loads assemblies.
/// </summary>
public interface IAssemblyLoader
{
/// <summary>
/// Gets a value indicating whether this loader supports
/// loading assemblies into an isolated context.
/// </summary>
bool SupportsIsolation { get; }
/// <summary>
/// Loads the given assembly from the given path.
/// </summary>
/// <param name="assemblyPath">
/// The path of the assembly to load.
/// </param>
/// <returns>
/// The loaded assembly, if possible; null otherwise.
/// </returns>
Assembly LoadAssembly(
string assemblyPath);
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// Implement to take part in extension discovery. The implementation will need to be registered with an
/// <see cref="IExtensionTypeProvider"/>.
/// </summary>
public interface IExtensionTypeObserver
{
/// <summary>
/// Called for each type found in an <see cref="IExtensionTypeProvider"/>.
/// </summary>
/// <param name="type">
/// The type to analyze.
/// </param>
/// <param name="sourceName">
/// The source of the given type.
/// </param>
void ProcessType(Type type, string sourceName);
/// <summary>
/// Called when type discovery is complete.
/// </summary>
void DiscoveryComplete();
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// Broadcasts discovered types for registered <see cref="IExtensionTypeObserver"/> to process.
/// </summary>
public interface IExtensionTypeProvider
{
/// <summary>
/// Registers an observer to receive types.
/// </summary>
/// <param name="observer">
/// The observer to register.
/// </param>
void RegisterTypeConsumer(IExtensionTypeObserver observer);
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
/// <summary>
/// Exposes SDK plug-ins.
/// </summary>
public interface IPlugInCatalog
{
/// <summary>
/// Gets a value indicating whether the catalog is loaded.
/// </summary>
bool IsLoaded { get; }
/// <summary>
/// Gets an enumeration of plug-ins.
/// </summary>
IEnumerable<CustomDataSourceReference> PlugIns { get; }
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Microsoft.Performance.SDK.Runtime.Discovery
{
public delegate bool TryCreateCustomDataSourceReferenceDelegate(
Type type,
out CustomDataSourceReference reference);
/// <summary>
/// This class registers to with a provider to receive Types that might be custom data sources.
/// The types are evaluated and stored as a <see cref="CustomDataSourceReference"/> when applicable.
/// </summary>
public sealed class ReflectionPlugInCatalog
: IPlugInCatalog,
IExtensionTypeObserver
{
private readonly TryCreateCustomDataSourceReferenceDelegate referenceFactory;
private readonly Dictionary<Type, CustomDataSourceReference> loadedDataSources;
public ReflectionPlugInCatalog(IExtensionTypeProvider extensionDiscovery)
: this(
extensionDiscovery,
CustomDataSourceReference.TryCreateReference)
{
}
public ReflectionPlugInCatalog(
IExtensionTypeProvider extensionDiscovery,
TryCreateCustomDataSourceReferenceDelegate referenceFactory)
{
Guard.NotNull(extensionDiscovery, nameof(extensionDiscovery));
Guard.NotNull(referenceFactory, nameof(referenceFactory));
this.referenceFactory = referenceFactory;
this.loadedDataSources = new Dictionary<Type, CustomDataSourceReference>();
this.IsLoaded = false;
extensionDiscovery.RegisterTypeConsumer(this);
}
public IEnumerable<CustomDataSourceReference> PlugIns => this.loadedDataSources.Values;
public bool IsLoaded { get; private set; }
public void ProcessType(Type type, string sourceName)
{
if (this.referenceFactory(type, out CustomDataSourceReference reference))
{
Debug.Assert(reference != null);
this.loadedDataSources.TryAdd(type, reference);
}
}
public void DiscoveryComplete()
{
this.IsLoaded = true;
}
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Enables building of dynamic tables.
/// <para />
/// Dynamic tables are normally created at runtime after processing the data source.
/// </summary>
public class DynamicTableBuilder
: TableBuilder,
IDynamicTableBuilder
{
private readonly Action<IDynamicTableBuilder> buildNewTable;
/// <param name="buildNewTable">
/// Build table action.
/// </param>
/// <param name="tableDescriptor">
/// <see cref="TableDescriptor"/> for the dynamic table.
/// </param>
/// <param name="dataSourceInfo">
/// <see cref="DataSourceInfo"/> associated with the dynamic table.
/// </param>
public DynamicTableBuilder(
Action<IDynamicTableBuilder> buildNewTable,
TableDescriptor tableDescriptor,
DataSourceInfo dataSourceInfo)
{
Guard.NotNull(buildNewTable, nameof(buildNewTable));
Guard.NotNull(tableDescriptor, nameof(tableDescriptor));
Guard.NotNull(dataSourceInfo, nameof(dataSourceInfo));
this.buildNewTable = buildNewTable;
this.DataSourceInfo = dataSourceInfo;
this.TableDescriptor = tableDescriptor;
}
/// <summary>
/// Description of the table to be created.
/// </summary>
public TableDescriptor TableDescriptor { get; }
/// <summary>
/// Data about the source to which the table will belong.
/// </summary>
public DataSourceInfo DataSourceInfo { get; }
/// <summary>
/// Options for table creation.
/// </summary>
public AddNewTableOption NewTableOption { get; private set; }
/// <summary>
/// Create the new table
/// </summary>
/// <param name="option">
/// Options for table creation.
/// </param>
public void AddDynamicTable(AddNewTableOption option)
{
this.NewTableOption = option;
this.buildNewTable(this);
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace Microsoft.Performance.SDK.Runtime
{
internal static class EnumerableExtensions
{
internal static IEnumerable<T> AsEnumerableSingleton<T>(this T self)
{
yield return self;
}
internal static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail)
{
foreach (var item in head)
{
yield return item;
}
yield return tail;
}
internal static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
}
}
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Represents the context of execution for
/// a custom data source.
/// </summary>
public sealed class ExecutionContext
: IFormattable
{
/// <summary>
/// Initializes a new instance of the <see cref="ExecutionContext"/>
/// class.
/// </summary>
public ExecutionContext(
IProgress<int> progress,
Func<ICustomDataProcessor, ILogger> loggerFactory,
ICustomDataSource customDataSource,
IEnumerable<IDataSource> dataSources,
IEnumerable<TableDescriptor> tablesToEnable,
IProcessorEnvironment processorEnvironment,
ProcessorOptions commandLineOptions)
{
Guard.NotNull(progress, nameof(progress));
Guard.NotNull(loggerFactory, nameof(loggerFactory));
Guard.NotNull(customDataSource, nameof(customDataSource));
Guard.NotNull(dataSources, nameof(dataSources));
Guard.All(dataSources, x => x != null, nameof(dataSources));
Guard.NotNull(tablesToEnable, nameof(tablesToEnable));
Guard.All(tablesToEnable, x => x != null, nameof(tablesToEnable));
Guard.NotNull(processorEnvironment, nameof(processorEnvironment));
Guard.NotNull(commandLineOptions, nameof(commandLineOptions));
this.ProgressReporter = progress;
this.LoggerFactory = loggerFactory;
this.CustomDataSource = customDataSource;
this.DataSources = dataSources.ToList().AsReadOnly();
this.TablesToEnable = tablesToEnable.ToList().AsReadOnly();
this.ProcessorEnvironment = processorEnvironment;
this.CommandLineOptions = commandLineOptions;
}
/// <summary>
/// Gets the instance to use to report
/// processing progress.
/// </summary>
public IProgress<int> ProgressReporter { get; }
/// <summary>
/// Gets the logger factory for the processor.
/// </summary>
public Func<ICustomDataProcessor, ILogger> LoggerFactory { get; }
/// <summary>
/// Gets the <see cref="ICustomDataSource"/> associated
/// with the given data items.
/// </summary>
public ICustomDataSource CustomDataSource { get; }
/// <summary>
/// Gets the <see cref="IDataSource"/>s that can be
/// processed by the <see cref="CustomDataSource"/>.
/// </summary>
public IEnumerable<IDataSource> DataSources { get; }
/// <summary>
/// Gets the collection of tables that are to be
/// enabled for the processing session.
/// </summary>
public IEnumerable<TableDescriptor> TablesToEnable { get; }
/// <summary>
/// The environment for the processor.
/// </summary>
public IProcessorEnvironment ProcessorEnvironment { get; }
/// <summary>
/// Gets the collection of command line parameters to pass
/// to the processor.
/// </summary>
public ProcessorOptions CommandLineOptions { get; }
/// <inheritdoc/>
public override string ToString()
{
return this.ToString("G");
}
/// <inheritdoc/>
public string ToString(string format)
{
return this.ToString(format, CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets the string representation of this instance.
/// </summary>
/// <param name="format">
/// The format specifier describing how to format the instance.
/// <para/>
/// F - format as a comma separated list of file paths.
/// <para/>
/// G - The default format. Returns the name of the custom data source.
/// </param>
/// <param name="formatProvider">
/// An object to provide formatting information. This parameter may be <c>null</c>.
/// </param>
/// <returns>
/// The string representation of this instance.
/// </returns>
public string ToString(string format, IFormatProvider formatProvider)
{
switch (format)
{
case "F":
return string.Join(",", this.DataSources.Select(x => x.GetUri()));
case "G":
case "g":
case null:
return this.CustomDataSource.TryGetName();
default:
throw new FormatException($"Unsupported format specified: '{format}'");
}
}
}
}

Просмотреть файл

@ -0,0 +1,228 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// Represents a container of the execution results from the custom data source.
/// </summary>
public sealed class ExecutionResult
{
/// <summary>
/// Initializes a new Execution result for success case.
/// </summary>
/// <param name="context">
/// <see cref="ExecutionContext"/> from the processing state.
/// </param>
/// <param name="dataSourceInfo">
/// <see cref="DataSourceInfo"/> from the custom data source being processed.
/// </param>
/// <param name="dataSourceInfoFailure">
/// <see cref="Exception"/> for the processing state (if available).
/// </param>
/// <param name="processor">
/// <see cref="ICustomDataProcessor"/> associated to the processing state.
/// </param>
/// <param name="enableFailures">
/// Dictionary representing the failures for <see cref="TableDescriptor"/> (if any).
/// </param>
/// <param name="metadataName">
/// Name of the custom data source being processed.
/// </param>
/// <param name="builtMetadataTables">
/// Collection of <see cref="MetadataTableBuilder"/>.
/// </param>
/// <param name="metadataException">
/// <see cref="Exception"/> for creating metadata tables (if available).
/// </param>
public ExecutionResult(
ExecutionContext context,
DataSourceInfo dataSourceInfo,
Exception dataSourceInfoFailure,
ICustomDataProcessor processor,
IDictionary<TableDescriptor, Exception> enableFailures,
string metadataName,
IEnumerable<MetadataTableBuilder> builtMetadataTables,
Exception metadataException)
: this(enableFailures, context.TablesToEnable)
{
Guard.NotNull(context, nameof(context));
Guard.NotNull(dataSourceInfo, nameof(dataSourceInfo));
Guard.NotNull(processor, nameof(processor));
Guard.NotNullOrWhiteSpace(metadataName, nameof(metadataName));
Guard.NotNull(builtMetadataTables, nameof(builtMetadataTables));
this.AssociatedWithCustomDataSource = true;
// Exception may be null.
this.Context = context;
this.Processor = processor;
this.DataSourceInfo = dataSourceInfo;
this.DataSourceInfoFailure = dataSourceInfoFailure;
this.MetadataName = metadataName;
this.BuiltMetadataTables = new ReadOnlyCollection<MetadataTableBuilder>(builtMetadataTables.ToList());
this.MetadataTableFailure = metadataException;
Debug.Assert(this.RequestedTables != null);
// If the processor contains tables that were generated while processing the data source, then add them to our RequestedTables.
// todo: RequestedTables - should we rename this? Just make it AvailableTables or something similar?
if (processor is IDataDerivedTables postProcessTables &&
postProcessTables.DataDerivedTables != null)
{
// do some extra validation on these table descriptors, as they might have been generated by hand
// rather than our usual library
//
foreach (var tableDescriptor in postProcessTables.DataDerivedTables)
{
if (tableDescriptor.PrebuiltTableConfigurations is null)
{
var tableConfigurations = new TableConfigurations(tableDescriptor.Guid)
{
Configurations = Enumerable.Empty<TableConfiguration>()
};
tableDescriptor.PrebuiltTableConfigurations = tableConfigurations;
}
}
// combine these new tables with any existing requested tables for this processor
this.RequestedTables = this.RequestedTables.Concat(postProcessTables.DataDerivedTables);
}
}
/// <summary>
/// Initializes a new faulting ExecutionResult.
/// </summary>
/// <param name="context">
/// <see cref="ExecutionContext"/> from the processing state.
/// </param>
/// <param name="processor">
/// <see cref="ICustomDataProcessor"/> associated to the processing state.
/// </param>
/// <param name="processorFault">
/// Faulting <see cref="Exception"/>.
/// </param>
public ExecutionResult(
ExecutionContext context,
ICustomDataProcessor processor,
Exception processorFault)
{
Guard.NotNull(context, nameof(context));
Guard.NotNull(processor, nameof(processor));
Guard.NotNull(processorFault, nameof(processorFault));
this.Context = context;
this.DataSourceInfo = DataSourceInfo.None;
this.Processor = processor;
this.RequestedTables = context.TablesToEnable.ToList().AsReadOnly();
this.ProcessorFault = processorFault;
this.EnableFailures = new ReadOnlyDictionary<TableDescriptor, Exception>(new Dictionary<TableDescriptor, Exception>());
this.BuiltMetadataTables = new List<MetadataTableBuilder>().AsReadOnly();
this.MetadataTableFailure = null;
}
/// <summary>
/// Stores a set of basic data that is always expected to be set.
/// </summary>
/// <param name="enableFailures">Tables that failed to be enabled.</param>
/// <param name="builtTables">Tables that were built.</param>
/// <param name="buildFailures">Tables that failed to build.</param>
public ExecutionResult(
IDictionary<TableDescriptor, Exception> enableFailures,
IEnumerable<TableDescriptor> requestedTables)
{
Guard.NotNull(enableFailures, nameof(enableFailures));
this.EnableFailures = new ReadOnlyDictionary<TableDescriptor, Exception>(enableFailures);
this.RequestedTables = requestedTables;
}
/// <summary>
/// Gets the tables that failed to enable.
/// </summary>
public IReadOnlyDictionary<TableDescriptor, Exception> EnableFailures { get; }
/// <summary>
/// Gets the collection of all tables that were requested by the application.
/// </summary>
public IEnumerable<TableDescriptor> RequestedTables { get; }
/// <summary>
/// When true, this object has additional data from a custom data source and
/// custom data processor.
/// </summary>
public bool AssociatedWithCustomDataSource { get; }
// ---- The properties below are associated with a custom data source and custom data processor ----
/// <summary>
/// Gets the context that produced this result.
/// </summary>
public ExecutionContext Context { get; }
/// <summary>
/// Gets the processor that produced this result.
/// </summary>
public ICustomDataProcessor Processor { get; }
/// <summary>
/// Gets the information about the data source (s).
/// </summary>
public DataSourceInfo DataSourceInfo { get; }
/// <summary>
/// Gets the failure that occurred, if any, while trying to
/// retrieve information about the data source from the processor.
/// This property will be <c>null</c> if <see cref="DataSourceInfo"/> is
/// populated.
/// </summary>
public Exception DataSourceInfoFailure { get; }
/// <summary>
/// Gets the name that describes the metadata.
/// </summary>
public string MetadataName { get; }
/// <summary>
/// Gets the metadata tables that were built by the processor.
/// </summary>
public IReadOnlyCollection<MetadataTableBuilder> BuiltMetadataTables { get; }
/// <summary>
/// Gets the <see cref="Exception"/> that occurred, if any, while building
/// metadata tables. This property will be <c>null</c> if <see cref="BuiltMetadataTables"/>
/// was successfully populated.
/// </summary>
public Exception MetadataTableFailure { get; }
/// <summary>
/// Gets a value indicating whether the processor itself faulted.
/// If the processor is faulted, then no tables will have been created.
/// Only <see cref="Context" />, <see cref="Processor"/>,
/// <see cref="RequestedTables" />, and <see cref="ProcessorFault" />
/// will be valid.
/// </summary>
public bool IsProcessorFaulted => this.ProcessorFault != null;
/// <summary>
/// The <see cref="Exception" /> that caused the processor to fault.
/// If this is an <see cref="ExtensionException" />, then the processor
/// itself reported the condition. Any other exception type means an
/// unexpected error occurred, and the plugin author should probably
/// be notified of the issue.
/// </summary>
public Exception ProcessorFault { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
namespace Microsoft.Performance.SDK.Runtime.Extensibility
{
/// <summary>
/// Exposes the types from an assembly.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay}")]
internal class AssemblyTypeSource
: ITypeSource
{
private readonly Assembly assembly;
public AssemblyTypeSource(Assembly assembly)
{
Guard.NotNull(assembly, nameof(assembly));
this.assembly = assembly;
}
public string Name => this.assembly.GetName().ToString();
public IEnumerable<Type> Types => this.assembly.GetTypes();
private string DebuggerDisplay => this.assembly.GetCodeBaseAsLocalPath();
}
}

Просмотреть файл

@ -0,0 +1,240 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
namespace Microsoft.Performance.SDK.Runtime.Extensibility
{
/// <summary>
/// Provides the framework for processing a source, passing along parsed data elements to registered data cookers.
/// </summary>
/// <typeparam name="T">
/// Data element type.
/// </typeparam>
/// <typeparam name="TContext">
/// Data context type.
/// </typeparam>
/// <typeparam name="TKey">
/// Data element key type.
/// </typeparam>
internal abstract class BaseSourceProcessingSession<T, TContext, TKey>
: ISourceProcessingSession<T, TContext, TKey>
where T : IKeyedDataType<TKey>
{
private Dictionary<TKey, List<ISourceDataCooker<T, TContext, TKey>>> activeCookerRegistry;
private List<ISourceDataCooker<T, TContext, TKey>> activeCookers;
private HashSet<ISourceDataCooker<T, TContext, TKey>> activeReceiveAllDataCookers;
private readonly Dictionary<DataCookerPath, ISourceDataCooker<T, TContext, TKey>>
cookerById = new Dictionary<DataCookerPath, ISourceDataCooker<T, TContext, TKey>>();
private readonly HashSet<ISourceDataCooker<T, TContext, TKey>>
registeredCookers = new HashSet<ISourceDataCooker<T, TContext, TKey>>();
private readonly HashSet<ISourceDataCooker<T, TContext, TKey>>
receiveAllDataCookers = new HashSet<ISourceDataCooker<T, TContext, TKey>>();
private readonly IEqualityComparer<TKey> keyEqualityComparer;
protected BaseSourceProcessingSession(
ISourceParser<T, TContext, TKey> sourceParser,
IEqualityComparer<TKey> comparer)
{
Guard.NotNull(sourceParser, nameof(sourceParser));
Guard.NotNull(comparer, nameof(comparer));
this.SourceParser = sourceParser;
this.keyEqualityComparer = comparer;
}
public ISourceParser<T, TContext, TKey> SourceParser { get; }
public IReadOnlyCollection<ISourceDataCooker<T, TContext, TKey>> RegisteredSourceDataCookers
=> this.RegisteredCookers;
public string Id => this.SourceParser.Id;
public Type DataElementType => this.SourceParser.DataElementType;
public Type DataContextType => this.SourceParser.DataContextType;
public Type DataKeyType => this.SourceParser.DataKeyType;
public int MaxSourceParseCount => this.SourceParser.MaxSourceParseCount;
protected IReadOnlyCollection<ISourceDataCooker<T, TContext, TKey>> RegisteredCookers => this.registeredCookers;
public void RegisterSourceDataCooker(ISourceDataCooker<T, TContext, TKey> dataCooker)
{
Guard.NotNull(dataCooker, nameof(dataCooker));
if (!StringComparer.Ordinal.Equals(dataCooker.Path.SourceParserId, SourceParser.Id))
{
throw new ArgumentException(
$"The {nameof(IDataCookerDescriptor.Path.SourceParserId)} of {nameof(dataCooker)} doesn't match "
+ $"{nameof(SourceParser)}.",
nameof(dataCooker));
}
if (!this.cookerById.ContainsKey(dataCooker.Path))
{
this.cookerById[dataCooker.Path] = dataCooker;
this.registeredCookers.Add(dataCooker);
if (dataCooker.Options.HasFlag(SourceDataCookerOptions.ReceiveAllDataElements))
{
this.receiveAllDataCookers.Add(dataCooker);
}
}
}
public ISourceDataCooker<T, TContext, TKey> GetSourceDataCooker(DataCookerPath cookerPath)
{
Guard.NotNull(cookerPath, nameof(cookerPath ));
this.cookerById.TryGetValue(cookerPath, out var cooker);
return cooker;
}
public DataProcessingResult ProcessDataElement(T data, TContext context, CancellationToken cancellationToken)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(context, nameof(context));
Guard.NotNull(cancellationToken, nameof(cancellationToken));
Debug.Assert(this.activeCookerRegistry != null);
Debug.Assert(this.activeReceiveAllDataCookers != null);
var key = data.GetKey();
if (!this.activeCookerRegistry.ContainsKey(key) && !this.receiveAllDataCookers.Any())
{
return DataProcessingResult.Ignored;
}
DataProcessingResult ProcessDataCookers(IEnumerable<ISourceDataCooker<T, TContext, TKey>> cookers)
{
DataProcessingResult dataResult = DataProcessingResult.Ignored;
foreach (var sourceDataCooker in cookers)
{
var result = sourceDataCooker.CookDataElement(data, context, cancellationToken);
if (result == DataProcessingResult.CorruptData)
{
return result;
}
if (result == DataProcessingResult.Processed)
{
dataResult = result;
}
}
return dataResult;
}
var keyedResult = DataProcessingResult.Ignored;
if (this.activeCookerRegistry.ContainsKey(key))
{
var sourceDataCookers = this.activeCookerRegistry[key];
keyedResult = ProcessDataCookers(sourceDataCookers);
if (keyedResult == DataProcessingResult.CorruptData)
{
return keyedResult;
}
}
var allDataResult = ProcessDataCookers(this.activeReceiveAllDataCookers);
if (allDataResult == DataProcessingResult.CorruptData)
{
return allDataResult;
}
if (keyedResult == DataProcessingResult.Processed || allDataResult == DataProcessingResult.Processed)
{
return DataProcessingResult.Processed;
}
return DataProcessingResult.Ignored;
}
public abstract void ProcessSource(ILogger logger, IProgress<int> progress, CancellationToken cancellationToken);
protected virtual void SetActiveDataCookers()
{
// this default implementation just adds all registered cookers
ClearActiveCookers(this.registeredCookers.Count);
foreach (var cooker in this.registeredCookers)
{
ActivateCooker(cooker);
}
}
protected void InitializeForSourceParsing()
{
SetActiveDataCookers();
this.RegisterCookers();
this.InitializeSourceParserForProcessing();
}
protected void ClearActiveCookers(int newCapacityCount)
{
this.activeCookers = new List<ISourceDataCooker<T, TContext, TKey>>(newCapacityCount);
this.activeReceiveAllDataCookers = new HashSet<ISourceDataCooker<T, TContext, TKey>>();
}
protected void ActivateCooker(ISourceDataCooker<T, TContext, TKey> sourceDataCooker)
{
Guard.NotNull(sourceDataCooker, nameof(sourceDataCooker));
this.activeCookers.Add(sourceDataCooker);
if (sourceDataCooker.Options.HasFlag(SourceDataCookerOptions.ReceiveAllDataElements))
{
this.activeReceiveAllDataCookers.Add(sourceDataCooker);
}
}
private void RegisterCookers()
{
this.activeCookerRegistry = new Dictionary<TKey, List<ISourceDataCooker<T, TContext, TKey>>>(this.keyEqualityComparer);
foreach (var sourceDataCooker in this.activeCookers)
{
if (sourceDataCooker.DataKeys == null)
{
continue;
}
if (sourceDataCooker.Options.HasFlag(SourceDataCookerOptions.ReceiveAllDataElements))
{
continue;
}
foreach (var key in sourceDataCooker.DataKeys)
{
if (!this.activeCookerRegistry.ContainsKey(key))
{
this.activeCookerRegistry.Add(key, new List<ISourceDataCooker<T, TContext, TKey>>());
}
this.activeCookerRegistry[key].Add(sourceDataCooker);
}
}
}
private void InitializeSourceParserForProcessing()
{
this.SourceParser.PrepareForProcessing(this.activeReceiveAllDataCookers.Any(), activeCookerRegistry.Keys);
}
}
}

Просмотреть файл

@ -0,0 +1,190 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Repository;
using Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.Tables;
using System.Diagnostics;
namespace Microsoft.Performance.SDK.Runtime.Extensibility
{
/// <summary>
/// This class implements IDataProcessorExtensibilitySupport.
/// </summary>
public class CustomDataProcessorExtensibilitySupport
: IDataProcessorExtensibilitySupport
{
private readonly ICustomDataProcessorWithSourceParser dataProcessor;
private readonly IDataExtensionRepository dataExtensionRepository;
private readonly Dictionary<TableDescriptor, ITableExtensionReference> tableReferences;
private DataExtensionRetrievalFactory dataExtensionRetrievalFactory;
private bool finalizedData;
/// <summary>
/// This constructor is associated with a given <see cref="ICustomDataProcessorWithSourceParser"/>, provided
/// as a parameter. <paramref name="dataExtensionRepository"/> is required to retrieve all available data
/// extensions.
/// </summary>
/// <param name="dataProcessor">
/// The data processor with which this object is associated.
/// </param>
/// <param name="dataExtensionRepository">
/// Provides access to a set of data extensions.
/// </param>
public CustomDataProcessorExtensibilitySupport(
ICustomDataProcessorWithSourceParser dataProcessor,
IDataExtensionRepository dataExtensionRepository)
{
this.dataProcessor = dataProcessor;
this.dataExtensionRepository = dataExtensionRepository;
this.tableReferences = new Dictionary<TableDescriptor, ITableExtensionReference>();
}
/// <inheritdoc />
public bool AddTable(TableDescriptor tableDescriptor)
{
lock (this.dataProcessor)
{
if (this.dataExtensionRetrievalFactory != null)
{
throw new InvalidOperationException($"Cannot add tables after calling {nameof(FinalizeTables)}.");
}
}
if (tableDescriptor.RequiredDataCookers.Any() || tableDescriptor.RequiredDataProcessors.Any())
{
if (TableExtensionReference.TryCreateReference(tableDescriptor.Type, true, out var tableReference))
{
this.tableReferences[tableDescriptor] = tableReference;
return true;
}
}
return false;
}
/// <inheritdoc />
public void FinalizeTables()
{
lock (this.dataProcessor)
{
if (this.finalizedData)
{
throw new InvalidOperationException($"{nameof(FinalizeTables)} may only be called once.");
}
this.finalizedData = true;
}
// When a plugin has multiple source parsers, then it's possible that the list of table references has some
// tables associated with a source parser other than the one associated with the data source that was
// opened. so just remove these tables and proceed - this is not an error case
//
var tablesToRemove = new List<TableDescriptor>();
foreach (var kvp in this.tableReferences)
{
var tableReference = kvp.Value;
tableReference.ProcessDependencies(this.dataExtensionRepository);
if (tableReference.Availability != DataExtensionAvailability.Available)
{
tablesToRemove.Add(kvp.Key);
continue;
}
// check that there are no external sources required
foreach (var cookerPath in tableReference.DependencyReferences.RequiredSourceDataCookerPaths)
{
if (!StringComparer.Ordinal.Equals(cookerPath.SourceParserId, this.dataProcessor.SourceParserId))
{
tablesToRemove.Add(kvp.Key);
break;
}
Debug.Assert(!string.IsNullOrWhiteSpace(cookerPath.SourceParserId));
}
}
foreach (var tableDescriptor in tablesToRemove)
{
this.tableReferences.Remove(tableDescriptor);
}
lock (this.dataProcessor)
{
if (this.dataExtensionRetrievalFactory == null)
{
this.dataExtensionRetrievalFactory =
new DataExtensionRetrievalFactory(this.dataProcessor, dataExtensionRepository);
}
}
}
/// <inheritdoc />
public IDataExtensionRetrieval GetDataExtensionRetrieval(TableDescriptor tableDescriptor)
{
lock (this.dataProcessor)
{
if (this.dataExtensionRetrievalFactory == null)
{
throw new InvalidOperationException(
$"{nameof(FinalizeTables)} must be called before calling " +
$"{nameof(GetDataExtensionRetrieval)}.");
}
}
if (!this.tableReferences.ContainsKey(tableDescriptor))
{
return null;
}
ITableExtensionReference reference = this.tableReferences[tableDescriptor];
Debug.Assert(reference != null);
return this.dataExtensionRetrievalFactory.CreateDataRetrievalForTable(reference);
}
/// <inheritdoc />
public ISet<DataCookerPath> GetAllRequiredSourceDataCookers()
{
var sourceCookerPaths = new HashSet<DataCookerPath>();
lock (this.dataProcessor)
{
if (!this.finalizedData)
{
throw new InvalidOperationException(
$"{nameof(FinalizeTables)} must be called before calling " +
$"{nameof(GetAllRequiredSourceDataCookers)}.");
}
}
foreach (var kvp in this.tableReferences)
{
var tableReference = kvp.Value;
Debug.Assert(tableReference.DependencyReferences != null);
foreach (var cookerPath in tableReference.DependencyReferences.RequiredSourceDataCookerPaths)
{
sourceCookerPaths.Add(cookerPath);
}
}
return sourceCookerPaths;
}
/// <inheritdoc />
public IEnumerable<TableDescriptor> GetAllRequiredTables()
{
return this.tableReferences.Keys;
}
}
}

Просмотреть файл

@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataProcessing;
namespace Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers
{
/// <summary>
/// This class adds data cooker specific functionality on top of the base
/// <see cref="DataExtensionReference{TDerived}"/>.
/// </summary>
/// <typeparam name="TDerived">
/// The class deriving from this type.
/// </typeparam>
internal abstract class BaseDataCookerReference<TDerived>
: DataExtensionReference<TDerived>
where TDerived : BaseDataCookerReference<TDerived>
{
protected BaseDataCookerReference(Type type)
: base(type)
{
}
protected BaseDataCookerReference(DataExtensionReference<TDerived> other)
: base(other)
{
}
public DataCookerPath Path { get; private set; }
public string Description { get; protected set; }
public override string Name => this.Path.ToString();
protected bool IsSourceDataCooker { get; set; }
public override bool Equals(TDerived other)
{
return base.Equals(other) &&
StringComparer.Ordinal.Equals(this.Path, other.Path) &&
StringComparer.Ordinal.Equals(this.Description, other.Description);
}
protected virtual void ValidateInstance(IDataCookerDescriptor instance)
{
// Create an instance just to pull out the descriptor without saving any references to it.
if (instance is null)
{
this.AddError($"Unable to create an instance of {this.Type}");
this.InitialAvailability = DataExtensionAvailability.Error;
return;
}
if (string.IsNullOrWhiteSpace(instance.Path.DataCookerId))
{
this.AddError("A data cooker Id may not be empty or null.");
this.InitialAvailability = DataExtensionAvailability.Error;
}
}
protected void InitializeDescriptorData(IDataCookerDescriptor descriptor)
{
Guard.NotNull(descriptor, nameof(descriptor));
this.Path = descriptor.Path;
this.Description = descriptor.Description;
if (this.IsSourceDataCooker)
{
if (string.IsNullOrEmpty(descriptor.Path.SourceParserId))
{
this.AddError($"A source data cooker's source Id must not be {nameof(DataCookerPath.EmptySourceParserId)}.");
this.InitialAvailability = DataExtensionAvailability.Error;
}
}
else
{
if (descriptor.Path.SourceParserId != DataCookerPath.EmptySourceParserId)
{
this.AddError($"A composite data cooker's source Id must be {nameof(DataCookerPath.EmptySourceParserId)}.");
this.InitialAvailability = DataExtensionAvailability.Error;
}
}
if (descriptor is IDataCookerDependent cookerRequirements)
{
foreach (var dataCookerPath in cookerRequirements.RequiredDataCookers)
{
this.AddRequiredDataCooker(dataCookerPath);
}
}
if (descriptor is IDataProcessorDependent processorRequirements)
{
foreach (var dataProcessorId in processorRequirements.RequiredDataProcessors)
{
this.AddRequiredDataProcessor(dataProcessorId);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using System;
using System.Diagnostics;
namespace Microsoft.Performance.SDK.Runtime.Extensibility.DataExtensions.DataCookers
{
internal class CompositeDataCookerReference
: BaseDataCookerReference<CompositeDataCookerReference>,
ICompositeDataCookerReference
{
protected readonly object instanceLock = new object();
internal static bool TryCreateReference(
Type candidateType,
out ICompositeDataCookerReference reference)
{
Guard.NotNull(candidateType, nameof(candidateType));
reference = null;
if (candidateType.IsPublicAndInstantiatableOfType(typeof(ICompositeDataCookerDescriptor)))
{
// There must be an empty, public constructor
if (candidateType.TryGetEmptyPublicConstructor(out var constructor))
{
try
{
reference = new CompositeDataCookerReference(candidateType);
}
catch (InvalidOperationException e)
{
Console.Error.WriteLine($"Cooker Disabled: {candidateType}.");
Console.Error.WriteLine($"{e.Message}");
return false;
}
}
}
return reference != null;
}
private CompositeDataCookerReference(Type type)
: base(type)
{
this.CreateInstance();
this.ValidateInstance(this.Instance);
if (this.Instance != null)
{
this.InitializeDescriptorData(this.Instance);
}
}
public CompositeDataCookerReference(CompositeDataCookerReference other)
: base(other)
{
this.InitializeDescriptorData(other);
lock (other.instanceLock)
{
this.Instance = other.Instance;
this.InstanceInitialized = other.InstanceInitialized;
}
}
protected ICompositeDataCookerDescriptor Instance { get; set; }
private bool InstanceInitialized { get; set; }
public IDataCooker GetOrCreateInstance(IDataExtensionRetrieval requiredData)
{
Guard.NotNull(requiredData, nameof(requiredData));
if (this.Instance == null)
{
return null;
}
if (!this.InstanceInitialized)
{
lock (this.instanceLock)
{
if (!this.InstanceInitialized)
{
this.Instance.OnDataAvailable(requiredData);
this.InstanceInitialized = true;
}
}
}
return this.Instance;
}
public override CompositeDataCookerReference CloneT()
{
return new CompositeDataCookerReference(this);
}
protected override void ValidateInstance(IDataCookerDescriptor instance)
{
base.ValidateInstance(instance);
if (!StringComparer.Ordinal.Equals(instance.Path.SourceParserId, DataCookerPath.EmptySourceParserId))
{
this.AddError($"Unable to create an instance of {this.Type}");
this.InitialAvailability = DataExtensionAvailability.Error;
}
}
private void CreateInstance()
{
this.Instance = Activator.CreateInstance(this.Type) as ICompositeDataCookerDescriptor;
Debug.Assert(this.Instance != null);
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше