Initial Code Commit.
This commit is contained in:
Родитель
aba940c914
Коммит
1b0319d3a6
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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>
|
|
@ -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
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);
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче