2
0
mirror of synced 2025-02-23 13:38:07 +00:00

Extracts core from disttest core.

This commit is contained in:
benbierens 2023-09-12 13:32:06 +02:00
parent 32ad778a91
commit dc1bed6861
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
28 changed files with 140 additions and 106 deletions

View File

@ -1,4 +1,4 @@
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
using Utils; using Utils;

View File

@ -1,5 +1,5 @@
//using DistTestCore.Marketplace; //using DistTestCore.Marketplace;
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
using Utils; using Utils;

View File

@ -1,4 +1,4 @@
using DistTestCore; using Core;
namespace CodexPlugin namespace CodexPlugin
{ {

View File

@ -1,6 +1,5 @@
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging;
using System.Collections; using System.Collections;
namespace CodexPlugin namespace CodexPlugin

View File

@ -1,4 +1,4 @@
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
@ -40,7 +40,13 @@ namespace CodexPlugin
public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup) public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup)
{ {
return null!; return SetupCodexNodes(1, setup)[0];
}
public ICodexNodeGroup SetupCodexNodes(int number, Action<ICodexSetup> setup)
{
var rc = StartCodexNodes(number, setup);
return WrapCodexContainers(rc);
} }
public ICodexNodeGroup SetupCodexNodes(int number) public ICodexNodeGroup SetupCodexNodes(int number)
@ -48,6 +54,5 @@ namespace CodexPlugin
var rc = StartCodexNodes(number, s => { }); var rc = StartCodexNodes(number, s => { });
return WrapCodexContainers(rc); return WrapCodexContainers(rc);
} }
} }
} }

View File

@ -21,7 +21,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" /> <ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,4 +1,4 @@
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;

View File

@ -1,5 +1,4 @@
using DistTestCore; using Core;
using DistTestCore.Logs;
using FileUtils; using FileUtils;
using Logging; using Logging;
using NUnit.Framework; using NUnit.Framework;
@ -12,8 +11,8 @@ namespace CodexPlugin
string GetName(); string GetName();
CodexDebugResponse GetDebugInfo(); CodexDebugResponse GetDebugInfo();
CodexDebugPeerResponse GetDebugPeer(string peerId); CodexDebugPeerResponse GetDebugPeer(string peerId);
ContentId UploadFile(TestFile file); ContentId UploadFile(TrackedFile file);
TestFile? DownloadContent(ContentId contentId, string fileLabel = ""); TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
void ConnectToPeer(IOnlineCodexNode node); void ConnectToPeer(IOnlineCodexNode node);
IDownloadedLog DownloadLog(int? tailLines = null); IDownloadedLog DownloadLog(int? tailLines = null);
//IMetricsAccess Metrics { get; } //IMetricsAccess Metrics { get; }
@ -62,7 +61,7 @@ namespace CodexPlugin
return CodexAccess.GetDebugPeer(peerId); return CodexAccess.GetDebugPeer(peerId);
} }
public ContentId UploadFile(TestFile file) public ContentId UploadFile(TrackedFile file)
{ {
using var fileStream = File.OpenRead(file.Filename); using var fileStream = File.OpenRead(file.Filename);
@ -80,7 +79,7 @@ namespace CodexPlugin
return new ContentId(response); return new ContentId(response);
} }
public TestFile? DownloadContent(ContentId contentId, string fileLabel = "") public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "")
{ {
var logMessage = $"Downloading for contentId: '{contentId.Id}'..."; var logMessage = $"Downloading for contentId: '{contentId.Id}'...";
Log(logMessage); Log(logMessage);
@ -104,7 +103,11 @@ namespace CodexPlugin
public IDownloadedLog DownloadLog(int? tailLines = null) public IDownloadedLog DownloadLog(int? tailLines = null)
{ {
return null!; // lifecycle.DownloadLog(CodexAccess.Container, tailLines); var workflow = tools.CreateWorkflow();
var file = tools.GetLog().CreateSubfile();
var logHandler = new LogDownloadHandler(CodexAccess.GetName(), file);
workflow.DownloadContainerLog(CodexAccess.Container, logHandler);
return logHandler.DownloadLog();
} }
public void BringOffline() public void BringOffline()
@ -142,7 +145,7 @@ namespace CodexPlugin
return multiAddress.Replace("0.0.0.0", peer.CodexAccess.Container.Pod.PodInfo.Ip); return multiAddress.Replace("0.0.0.0", peer.CodexAccess.Container.Pod.PodInfo.Ip);
} }
private void DownloadToFile(string contentId, TestFile file) private void DownloadToFile(string contentId, TrackedFile file)
{ {
using var fileStream = File.OpenWrite(file.Filename); using var fileStream = File.OpenWrite(file.Filename);
try try

View File

@ -1,4 +1,4 @@
using DistTestCore; using Core;
using KubernetesWorkflow; using KubernetesWorkflow;
namespace CodexPlugin namespace CodexPlugin
@ -20,6 +20,11 @@ namespace CodexPlugin
return Plugin(pluginInterface).SetupCodexNode(setup); return Plugin(pluginInterface).SetupCodexNode(setup);
} }
public static ICodexNodeGroup SetupCodexNodes(this PluginInterface pluginInterface, int number, Action<ICodexSetup> setup)
{
return Plugin(pluginInterface).SetupCodexNodes(number, setup);
}
public static ICodexNodeGroup SetupCodexNodes(this PluginInterface pluginInterface, int number) public static ICodexNodeGroup SetupCodexNodes(this PluginInterface pluginInterface, int number)
{ {
return Plugin(pluginInterface).SetupCodexNodes(number); return Plugin(pluginInterface).SetupCodexNodes(number);

14
Core/Core.csproj Normal file
View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
namespace DistTestCore namespace Core
{ {
public abstract class DefaultContainerRecipe : ContainerRecipeFactory public abstract class DefaultContainerRecipe : ContainerRecipeFactory
{ {

View File

@ -1,11 +1,10 @@
using Logging; using Logging;
using NUnit.Framework;
namespace DistTestCore.Logs namespace Core
{ {
public interface IDownloadedLog public interface IDownloadedLog
{ {
void AssertLogContains(string expectedString); bool DoesLogContain(string expectedString);
string[] FindLinesThatContain(params string[] tags); string[] FindLinesThatContain(params string[] tags);
void DeleteFile(); void DeleteFile();
} }
@ -13,15 +12,13 @@ namespace DistTestCore.Logs
public class DownloadedLog : IDownloadedLog public class DownloadedLog : IDownloadedLog
{ {
private readonly LogFile logFile; private readonly LogFile logFile;
private readonly string owner;
public DownloadedLog(LogFile logFile, string owner) public DownloadedLog(LogFile logFile)
{ {
this.logFile = logFile; this.logFile = logFile;
this.owner = owner;
} }
public void AssertLogContains(string expectedString) public bool DoesLogContain(string expectedString)
{ {
using var file = File.OpenRead(logFile.FullFilename); using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file); using var streamReader = new StreamReader(file);
@ -29,11 +26,12 @@ namespace DistTestCore.Logs
var line = streamReader.ReadLine(); var line = streamReader.ReadLine();
while (line != null) while (line != null)
{ {
if (line.Contains(expectedString)) return; if (line.Contains(expectedString)) return true;
line = streamReader.ReadLine(); line = streamReader.ReadLine();
} }
Assert.Fail($"{owner} Unable to find string '{expectedString}' in CodexNode log file {logFile.FullFilename}"); //Assert.Fail($"{owner} Unable to find string '{expectedString}' in CodexNode log file {logFile.FullFilename}");
return false;
} }
public string[] FindLinesThatContain(params string[] tags) public string[] FindLinesThatContain(params string[] tags)

View File

@ -5,7 +5,7 @@ using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using Utils; using Utils;
namespace DistTestCore namespace Core
{ {
public class Http public class Http
{ {

View File

@ -1,25 +1,23 @@
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
namespace DistTestCore.Logs namespace Core
{ {
public class LogDownloadHandler : LogHandler, ILogHandler public class LogDownloadHandler : LogHandler, ILogHandler
{ {
private readonly RunningContainer container;
private readonly LogFile log; private readonly LogFile log;
public LogDownloadHandler(RunningContainer container, string description, LogFile log) public LogDownloadHandler(string description, LogFile log)
{ {
this.container = container;
this.log = log; this.log = log;
log.Write($"{description} -->> {log.FullFilename}"); log.Write($"{description} -->> {log.FullFilename}");
log.WriteRaw(description); log.WriteRaw(description);
} }
public DownloadedLog DownloadLog() public IDownloadedLog DownloadLog()
{ {
return new DownloadedLog(log, container.Name); return new DownloadedLog(log);
} }
protected override void ProcessLine(string line) protected override void ProcessLine(string line)

View File

@ -1,4 +1,4 @@
namespace DistTestCore namespace Core
{ {
public abstract class PluginInterface public abstract class PluginInterface
{ {

View File

@ -4,7 +4,7 @@ using Logging;
using System.Reflection; using System.Reflection;
using Utils; using Utils;
namespace DistTestCore namespace Core
{ {
public class PluginManager public class PluginManager
{ {

View File

@ -1,12 +1,5 @@
using NUnit.Framework; namespace Core
namespace DistTestCore
{ {
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class UseLongTimeoutsAttribute : PropertyAttribute
{
}
public interface ITimeSet public interface ITimeSet
{ {
TimeSpan HttpCallTimeout(); TimeSpan HttpCallTimeout();
@ -49,37 +42,4 @@ namespace DistTestCore
return TimeSpan.FromSeconds(30); return TimeSpan.FromSeconds(30);
} }
} }
public class LongTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromHours(2);
}
public TimeSpan HttpCallRetryTime()
{
return TimeSpan.FromHours(5);
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(2);
}
public TimeSpan WaitForK8sServiceDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(15);
}
public TimeSpan WaitForMetricTimeout()
{
return TimeSpan.FromMinutes(5);
}
}
} }

View File

@ -1,6 +1,4 @@
using KubernetesWorkflow; using Core;
using System.Net.NetworkInformation;
using Utils;
namespace DistTestCore namespace DistTestCore
{ {

View File

@ -1,4 +1,4 @@
using DistTestCore.Logs; using Core;
using FileUtils; using FileUtils;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
@ -92,7 +92,7 @@ namespace DistTestCore
} }
} }
public TestFile GenerateTestFile(ByteSize size, string label = "") public TrackedFile GenerateTestFile(ByteSize size, string label = "")
{ {
return Get().FileManager.GenerateTestFile(size, label); return Get().FileManager.GenerateTestFile(size, label);
} }

View File

@ -19,6 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\FileUtils\FileUtils.csproj" /> <ProjectReference Include="..\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" /> <ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
<ProjectReference Include="..\Logging\Logging.csproj" /> <ProjectReference Include="..\Logging\Logging.csproj" />

View File

@ -1,6 +1,6 @@
using NUnit.Framework; using NUnit.Framework;
namespace DistTestCore.Logs namespace DistTestCore
{ {
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DontDownloadLogsAndMetricsOnFailureAttribute : PropertyAttribute public class DontDownloadLogsAndMetricsOnFailureAttribute : PropertyAttribute

View File

@ -0,0 +1,37 @@
using Core;
namespace DistTestCore
{
public class LongTimeSet : ITimeSet
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromHours(2);
}
public TimeSpan HttpCallRetryTime()
{
return TimeSpan.FromHours(5);
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(2);
}
public TimeSpan WaitForK8sServiceDelay()
{
return TimeSpan.FromSeconds(10);
}
public TimeSpan K8sOperationTimeout()
{
return TimeSpan.FromMinutes(15);
}
public TimeSpan WaitForMetricTimeout()
{
return TimeSpan.FromMinutes(5);
}
}
}

View File

@ -0,0 +1,9 @@
using NUnit.Framework;
namespace DistTestCore
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class UseLongTimeoutsAttribute : PropertyAttribute
{
}
}

View File

@ -1,4 +1,5 @@
using FileUtils; using Core;
using FileUtils;
using KubernetesWorkflow; using KubernetesWorkflow;
using Logging; using Logging;
using Utils; using Utils;

View File

@ -6,8 +6,8 @@ namespace FileUtils
{ {
public interface IFileManager public interface IFileManager
{ {
TestFile CreateEmptyTestFile(string label = ""); TrackedFile CreateEmptyTestFile(string label = "");
TestFile GenerateTestFile(ByteSize size, string label = ""); TrackedFile GenerateTestFile(ByteSize size, string label = "");
void DeleteAllTestFiles(); void DeleteAllTestFiles();
void ScopedFiles(Action action); void ScopedFiles(Action action);
T ScopedFiles<T>(Func<T> action); T ScopedFiles<T>(Func<T> action);
@ -20,7 +20,7 @@ namespace FileUtils
private readonly Random random = new Random(); private readonly Random random = new Random();
private readonly ILog log; private readonly ILog log;
private readonly string folder; private readonly string folder;
private readonly List<List<TestFile>> fileSetStack = new List<List<TestFile>>(); private readonly List<List<TrackedFile>> fileSetStack = new List<List<TrackedFile>>();
public FileManager(ILog log, string rootFolder) public FileManager(ILog log, string rootFolder)
{ {
@ -30,16 +30,16 @@ namespace FileUtils
this.log = log; this.log = log;
} }
public TestFile CreateEmptyTestFile(string label = "") public TrackedFile CreateEmptyTestFile(string label = "")
{ {
var path = Path.Combine(folder, Guid.NewGuid().ToString() + "_test.bin"); var path = Path.Combine(folder, Guid.NewGuid().ToString() + "_test.bin");
var result = new TestFile(log, path, label); var result = new TrackedFile(log, path, label);
File.Create(result.Filename).Close(); File.Create(result.Filename).Close();
if (fileSetStack.Any()) fileSetStack.Last().Add(result); if (fileSetStack.Any()) fileSetStack.Last().Add(result);
return result; return result;
} }
public TestFile GenerateTestFile(ByteSize size, string label) public TrackedFile GenerateTestFile(ByteSize size, string label)
{ {
var sw = Stopwatch.Begin(log); var sw = Stopwatch.Begin(log);
var result = GenerateFile(size, label); var result = GenerateFile(size, label);
@ -69,7 +69,7 @@ namespace FileUtils
private void PushFileSet() private void PushFileSet()
{ {
fileSetStack.Add(new List<TestFile>()); fileSetStack.Add(new List<TrackedFile>());
} }
private void PopFileSet() private void PopFileSet()
@ -88,7 +88,7 @@ namespace FileUtils
} }
} }
private TestFile GenerateFile(ByteSize size, string label) private TrackedFile GenerateFile(ByteSize size, string label)
{ {
var result = CreateEmptyTestFile(label); var result = CreateEmptyTestFile(label);
CheckSpaceAvailable(result, size); CheckSpaceAvailable(result, size);
@ -97,7 +97,7 @@ namespace FileUtils
return result; return result;
} }
private void CheckSpaceAvailable(TestFile testFile, ByteSize size) private void CheckSpaceAvailable(TrackedFile testFile, ByteSize size)
{ {
var file = new FileInfo(testFile.Filename); var file = new FileInfo(testFile.Filename);
var drive = new DriveInfo(file.Directory!.Root.FullName); var drive = new DriveInfo(file.Directory!.Root.FullName);
@ -115,7 +115,7 @@ namespace FileUtils
} }
} }
private void GenerateFileBytes(TestFile result, ByteSize size) private void GenerateFileBytes(TrackedFile result, ByteSize size)
{ {
long bytesLeft = size.SizeInBytes; long bytesLeft = size.SizeInBytes;
int chunkSize = ChunkSize; int chunkSize = ChunkSize;
@ -135,7 +135,7 @@ namespace FileUtils
} }
} }
private void AppendRandomBytesToFile(TestFile result, long length) private void AppendRandomBytesToFile(TrackedFile result, long length)
{ {
var bytes = new byte[length]; var bytes = new byte[length];
random.NextBytes(bytes); random.NextBytes(bytes);

View File

@ -4,11 +4,11 @@ using Utils;
namespace FileUtils namespace FileUtils
{ {
public class TestFile public class TrackedFile
{ {
private readonly ILog log; private readonly ILog log;
public TestFile(ILog log, string filename, string label) public TrackedFile(ILog log, string filename, string label)
{ {
this.log = log; this.log = log;
Filename = filename; Filename = filename;
@ -18,7 +18,7 @@ namespace FileUtils
public string Filename { get; } public string Filename { get; }
public string Label { get; } public string Label { get; }
public void AssertIsEqual(TestFile? actual) public void AssertIsEqual(TrackedFile? actual)
{ {
var sw = Stopwatch.Begin(log); var sw = Stopwatch.Begin(log);
try try
@ -27,7 +27,7 @@ namespace FileUtils
} }
finally finally
{ {
sw.End($"{nameof(TestFile)}.{nameof(AssertIsEqual)}"); sw.End($"{nameof(TrackedFile)}.{nameof(AssertIsEqual)}");
} }
} }
@ -38,7 +38,7 @@ namespace FileUtils
return $"'{Filename}'{sizePostfix}"; return $"'{Filename}'{sizePostfix}";
} }
private void AssertEqual(TestFile? actual) private void AssertEqual(TrackedFile? actual)
{ {
if (actual == null) Assert.Fail("TestFile is null."); if (actual == null) Assert.Fail("TestFile is null.");
if (actual == this || actual!.Filename == Filename) Assert.Fail("TestFile is compared to itself."); if (actual == this || actual!.Filename == Filename) Assert.Fail("TestFile is compared to itself.");

View File

@ -7,7 +7,7 @@ namespace KubernetesWorkflow
{ {
RunningContainers Start(int numberOfContainers, Location location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig); RunningContainers Start(int numberOfContainers, Location location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig);
void Stop(RunningContainers runningContainers); void Stop(RunningContainers runningContainers);
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines); void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null);
string ExecuteCommand(RunningContainer container, string command, params string[] args); string ExecuteCommand(RunningContainer container, string command, params string[] args);
void DeleteNamespace(); void DeleteNamespace();
void DeleteNamespacesStartingWith(string namespacePrefix); void DeleteNamespacesStartingWith(string namespacePrefix);
@ -53,7 +53,7 @@ namespace KubernetesWorkflow
}); });
} }
public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines) public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null)
{ {
K8s(controller => K8s(controller =>
{ {

View File

@ -23,9 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArgsUniform", "ArgsUniform\
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDownloader", "CodexNetDownloader\CodexNetDownloader.csproj", "{6CDF35D2-906A-4285-8E1F-4794588B948B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDownloader", "CodexNetDownloader\CodexNetDownloader.csproj", "{6CDF35D2-906A-4285-8E1F-4794588B948B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileUtils", "FileUtils\FileUtils.csproj", "{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileUtils", "FileUtils\FileUtils.csproj", "{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexPlugin", "CodexPlugin\CodexPlugin.csproj", "{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexPlugin", "CodexPlugin\CodexPlugin.csproj", "{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -81,6 +83,10 @@ Global
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.Build.0 = Release|Any CPU {DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.Build.0 = Release|Any CPU
{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE