Good progress

This commit is contained in:
ThatBen 2023-09-11 16:57:57 +02:00
parent 83d184177a
commit 48dda1735c
19 changed files with 332 additions and 179 deletions

View File

@ -7,11 +7,11 @@ namespace CodexPlugin
{
public class CodexAccess : ILogHandler
{
private readonly BaseLog log;
private readonly ILog log;
private readonly ITimeSet timeSet;
private bool hasContainerCrashed;
public CodexAccess(BaseLog log, RunningContainer container, ITimeSet timeSet, Address address)
public CodexAccess(ILog log, RunningContainer container, ITimeSet timeSet, Address address)
{
this.log = log;
Container = container;

View File

@ -3,7 +3,7 @@ namespace CodexPlugin
{
public interface ICodexNodeFactory
{
//OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
}
public class CodexNodeFactory : ICodexNodeFactory
@ -19,11 +19,11 @@ namespace CodexPlugin
// this.marketplaceAccessFactory = marketplaceAccessFactory;
//}
//public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
//{
public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
{
//var metricsAccess = metricsAccessFactory.CreateMetricsAccess(access.Container);
//var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(access);
// return new OnlineCodexNode(lifecycle, access, group, metricsAccess, marketplaceAccess);
//}
return new OnlineCodexNode(/*lifecycle,*/ access, group/*, metricsAccess, marketplaceAccess*/);
}
}
}

View File

@ -1,11 +1,13 @@
using KubernetesWorkflow;
using DistTestCore;
using KubernetesWorkflow;
using Logging;
using System.Collections;
namespace CodexPlugin
{
public interface ICodexNodeGroup : IEnumerable<IOnlineCodexNode>
{
ICodexSetup BringOffline();
void BringOffline();
IOnlineCodexNode this[int index] { get; }
}
@ -13,12 +15,12 @@ namespace CodexPlugin
{
//private readonly TestLifecycle lifecycle;
public CodexNodeGroup(/*TestLifecycle lifecycle, */CodexSetup setup, RunningContainers[] containers, ICodexNodeFactory codexNodeFactory)
public CodexNodeGroup(/*TestLifecycle lifecycle, CodexSetup setup,*/ILog log, ITimeSet timeSet, RunningContainers[] containers, ICodexNodeFactory codexNodeFactory)
{
//this.lifecycle = lifecycle;
Setup = setup;
//Setup = setup;
Containers = containers;
Nodes = containers.Containers().Select(c => CreateOnlineCodexNode(c, codexNodeFactory)).ToArray();
Nodes = containers.Containers().Select(c => CreateOnlineCodexNode(c, log, timeSet, codexNodeFactory)).ToArray();
Version = new CodexDebugVersionResponse();
}
@ -30,20 +32,18 @@ namespace CodexPlugin
}
}
public ICodexSetup BringOffline()
public void BringOffline()
{
//lifecycle.CodexStarter.BringOffline(this);
var result = Setup;
//var result = Setup;
// Clear everything. Prevent accidental use.
Setup = null!;
//Setup = null!;
Nodes = Array.Empty<OnlineCodexNode>();
Containers = null!;
return result;
}
public CodexSetup Setup { get; private set; }
//public CodexSetup Setup { get; private set; }
public RunningContainers[] Containers { get; private set; }
public OnlineCodexNode[] Nodes { get; private set; }
public CodexDebugVersionResponse Version { get; private set; }
@ -78,11 +78,10 @@ namespace CodexPlugin
Version = first;
}
private OnlineCodexNode CreateOnlineCodexNode(RunningContainer c, ICodexNodeFactory factory)
private OnlineCodexNode CreateOnlineCodexNode(RunningContainer c, ILog log, ITimeSet timeSet, ICodexNodeFactory factory)
{
//var access = new CodexAccess(lifecycle.Log, c, lifecycle.TimeSet, lifecycle.Configuration.GetAddress(c));
//return factory.CreateOnlineCodexNode(access, this);
return null!;
var access = new CodexAccess(log, c, timeSet, c.Address);
return factory.CreateOnlineCodexNode(access, this);
}
}
}

View File

@ -1,26 +1,32 @@
using KubernetesWorkflow;
using DistTestCore;
using KubernetesWorkflow;
using Logging;
namespace CodexPlugin
{
public class CodexStarter //: BaseStarter
public class CodexStarter
{
private readonly IPluginActions pluginActions;
//public CodexStarter(TestLifecycle lifecycle)
// : base(lifecycle)
//{
//}
public List<CodexNodeGroup> RunningGroups { get; } = new List<CodexNodeGroup>();
public CodexStarter(IPluginActions pluginActions)
{
this.pluginActions = pluginActions;
}
public ICodexNodeGroup BringOnline(CodexSetup codexSetup)
public RunningContainers[] BringOnline(CodexSetup codexSetup)
{
//LogSeparator();
//LogStart($"Starting {codexSetup.Describe()}...");
//var gethStartResult = lifecycle.GethStarter.BringOnlineMarketplaceFor(codexSetup);
//var startupConfig = CreateStartupConfig(gethStartResult, codexSetup);
var startupConfig = CreateStartupConfig(/*gethStartResult,*/ codexSetup);
//var containers = StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location);
return StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location);
//var metricAccessFactory = CollectMetrics(codexSetup, containers);
@ -38,7 +44,26 @@ namespace CodexPlugin
//LogSeparator();
//return group;
return null!;
}
public ICodexNodeGroup WrapCodexContainers(RunningContainers[] containers)
{
//var metricAccessFactory = CollectMetrics(codexSetup, containers);
var codexNodeFactory = new CodexNodeFactory();// (lifecycle, metricAccessFactory, gethStartResult.MarketplaceAccessFactory);
return CreateCodexGroup(/*codexSetup,*/ containers, codexNodeFactory);
//lifecycle.SetCodexVersion(group.Version);
//var nl = Environment.NewLine;
//var podInfos = string.Join(nl, containers.Containers().Select(c => $"Container: '{c.Name}' runs at '{c.Pod.PodInfo.K8SNodeName}'={c.Pod.PodInfo.Ip}"));
//LogEnd($"Started {codexSetup.NumberOfNodes} nodes " +
// $"of image '{containers.Containers().First().Recipe.Image}' " +
// $"and version '{group.Version}'{nl}" +
// podInfos);
//LogSeparator();
//return group;
}
public void BringOffline(CodexNodeGroup group)
@ -50,7 +75,6 @@ namespace CodexPlugin
// StopCrashWatcher(c);
// workflow.Stop(c);
//}
//RunningGroups.Remove(group);
//LogEnd("Stopped.");
}
@ -58,8 +82,6 @@ namespace CodexPlugin
{
//var workflow = CreateWorkflow();
//workflow.DeleteTestResources();
//RunningGroups.Clear();
}
public void DownloadLog(RunningContainer container, ILogHandler logHandler, int? tailLines)
@ -82,52 +104,51 @@ namespace CodexPlugin
// return new CodexNodeMetricsAccessFactory(lifecycle, runningContainers);
//}
//private StartupConfig CreateStartupConfig(GethStartResult gethStartResult, CodexSetup codexSetup)
//{
// var startupConfig = new StartupConfig();
// startupConfig.NameOverride = codexSetup.NameOverride;
// startupConfig.Add(codexSetup);
private StartupConfig CreateStartupConfig(/*GethStartResult gethStartResult, */ CodexSetup codexSetup)
{
var startupConfig = new StartupConfig();
startupConfig.NameOverride = codexSetup.NameOverride;
startupConfig.CreateCrashWatcher = true;
startupConfig.Add(codexSetup);
//startupConfig.Add(gethStartResult);
// return startupConfig;
//}
return startupConfig;
}
//private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, Location location)
//{
// var result = new List<RunningContainers>();
// var recipe = new CodexContainerRecipe();
// for (var i = 0; i < numberOfNodes; i++)
// {
// var workflow = CreateWorkflow();
// var rc = workflow.Start(1, location, recipe, startupConfig);
// CreateCrashWatcher(workflow, rc);
// result.Add(rc);
// }
// return result.ToArray();
//}
private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, Location location)
{
var result = new List<RunningContainers>();
var recipe = new CodexContainerRecipe();
for (var i = 0; i < numberOfNodes; i++)
{
var workflow = pluginActions.CreateWorkflow();
result.Add(workflow.Start(1, location, recipe, startupConfig));
}
return result.ToArray();
}
//private CodexNodeGroup CreateCodexGroup(CodexSetup codexSetup, RunningContainers[] runningContainers, CodexNodeFactory codexNodeFactory)
//{
// var group = new CodexNodeGroup(lifecycle, codexSetup, runningContainers, codexNodeFactory);
// RunningGroups.Add(group);
private CodexNodeGroup CreateCodexGroup(/*CodexSetup codexSetup, */RunningContainers[] runningContainers, CodexNodeFactory codexNodeFactory)
{
var group = new CodexNodeGroup(pluginActions.GetLog(), pluginActions.GetTimeSet(), /*lifecycle, codexSetup,*/ runningContainers, codexNodeFactory);
// try
// {
// Stopwatch.Measure(lifecycle.Log, "EnsureOnline", group.EnsureOnline, debug: true);
// }
// catch
// {
// CodexNodesNotOnline(runningContainers);
// throw;
// }
try
{
Stopwatch.Measure(pluginActions.GetLog(), "EnsureOnline", group.EnsureOnline, debug: true);
}
catch
{
CodexNodesNotOnline(runningContainers);
throw;
}
// return group;
//}
return group;
}
//private void CodexNodesNotOnline(RunningContainers[] runningContainers)
//{
// Log("Codex nodes failed to start");
private void CodexNodesNotOnline(RunningContainers[] runningContainers)
{
pluginActions.GetLog().Log("Codex nodes failed to start");
// todo:
//foreach (var container in runningContainers.Containers()) lifecycle.DownloadLog(container);
//}
}
//private StartupWorkflow CreateWorkflow()
//{
@ -139,12 +160,6 @@ namespace CodexPlugin
// Log("----------------------------------------------------------------------------");
//}
//private void CreateCrashWatcher(StartupWorkflow workflow, RunningContainers rc)
//{
// var c = rc.Containers.Single();
// c.CrashWatcher = workflow.CreateCrashWatcher(c);
//}
//private void StopCrashWatcher(RunningContainers containers)
//{
// foreach (var c in containers.Containers)

View File

@ -5,24 +5,26 @@ namespace CodexPlugin
{
public static class DistTestExtensions
{
public static RunningContainers StartCodexNodes(this DistTest distTest, int number, Action<ICodexSetup> setup)
public static Plugin Plugin { get; internal set; } = null!;
public static RunningContainers[] StartCodexNodes(this DistTest distTest, int number, Action<ICodexSetup> setup)
{
return null!;
return Plugin.StartCodexNodes(number, setup);
}
public static ICodexNodeGroup WrapCodexContainers(this DistTest distTest, RunningContainers containers)
{
return null!;
return Plugin.WrapCodexContainers(containers);
}
public static IOnlineCodexNode SetupCodexNode(this DistTest distTest, Action<ICodexSetup> setup)
{
return null!;
return Plugin.SetupCodexNode(setup);
}
public static ICodexNodeGroup SetupCodexNodes(this DistTest distTest, int number)
{
return null!;
return Plugin.SetupCodexNodes(number);
}
}
}

View File

@ -1,6 +1,5 @@
using DistTestCore.Logs;
using FileUtils;
using Logging;
using NUnit.Framework;
using Utils;
@ -18,7 +17,7 @@ namespace CodexPlugin
//IMetricsAccess Metrics { get; }
//IMarketplaceAccess Marketplace { get; }
CodexDebugVersionResponse Version { get; }
ICodexSetup BringOffline();
void BringOffline();
}
public class OnlineCodexNode : IOnlineCodexNode
@ -108,13 +107,13 @@ namespace CodexPlugin
return null!; // lifecycle.DownloadLog(CodexAccess.Container, tailLines);
}
public ICodexSetup BringOffline()
public void BringOffline()
{
if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " +
"individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " +
"available for codex-nodes in groups of 1.");
return Group.BringOffline();
Group.BringOffline();
}
public void EnsureOnlineGetVersionResponse()

40
CodexPlugin/Plugin.cs Normal file
View File

@ -0,0 +1,40 @@
using DistTestCore;
using KubernetesWorkflow;
namespace CodexPlugin
{
public class Plugin : IProjectPlugin
{
private readonly CodexStarter codexStarter;
public Plugin(IPluginActions actions)
{
codexStarter = new CodexStarter(actions);
DistTestExtensions.Plugin = this;
}
public RunningContainers[] StartCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
{
var codexSetup = new CodexSetup(numberOfNodes, CodexLogLevel.Trace);
setup(codexSetup);
return codexStarter.BringOnline(codexSetup);
}
public ICodexNodeGroup WrapCodexContainers(RunningContainers[] containers)
{
return codexStarter.WrapCodexContainers(containers);
}
public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup)
{
return null!;
}
public ICodexNodeGroup SetupCodexNodes(int number)
{
var rc = StartCodexNodes(1, s => { });
return WrapCodexContainers(rc);
}
}
}

View File

@ -12,7 +12,6 @@ namespace DistTestCore
private readonly string dataFilesPath;
//private readonly CodexLogLevel codexLogLevel;
private readonly string k8sNamespacePrefix;
private static RunnerLocation? runnerLocation = null;
public Configuration()
{
@ -59,20 +58,6 @@ namespace DistTestCore
// return codexLogLevel;
//}
public Address GetAddress(RunningContainer container)
{
if (runnerLocation == null)
{
runnerLocation = RunnerLocationUtils.DetermineRunnerLocation(container);
}
if (runnerLocation == RunnerLocation.InternalToCluster)
{
return container.ClusterInternalAddress;
}
return container.ClusterExternalAddress;
}
private static string GetEnvVarOrDefault(string varName, string defaultValue)
{
var v = Environment.GetEnvironmentVariable(varName);
@ -88,56 +73,5 @@ namespace DistTestCore
}
}
public enum RunnerLocation
{
ExternalToCluster,
InternalToCluster,
}
public static class RunnerLocationUtils
{
private static bool alreadyDidThat = false;
public static RunnerLocation DetermineRunnerLocation(RunningContainer container)
{
// We want to be sure we don't ping more often than strictly necessary.
// If we have already determined the location during this application
// lifetime, don't do it again.
if (alreadyDidThat) throw new Exception("We already did that.");
alreadyDidThat = true;
if (PingHost(container.Pod.PodInfo.Ip))
{
return RunnerLocation.InternalToCluster;
}
if (PingHost(Format(container.ClusterExternalAddress)))
{
return RunnerLocation.ExternalToCluster;
}
throw new Exception("Unable to determine runner location.");
}
private static string Format(Address host)
{
return host.Host
.Replace("http://", "")
.Replace("https://", "");
}
private static bool PingHost(string host)
{
try
{
using var pinger = new Ping();
PingReply reply = pinger.Send(host);
return reply.Status == IPStatus.Success;
}
catch (PingException)
{
}
return false;
}
}
}

View File

@ -9,19 +9,19 @@ namespace DistTestCore
{
public class Http
{
private readonly BaseLog log;
private readonly ILog log;
private readonly ITimeSet timeSet;
private readonly Address address;
private readonly string baseUrl;
private readonly Action<HttpClient> onClientCreated;
private readonly string? logAlias;
public Http(BaseLog log, ITimeSet timeSet, Address address, string baseUrl, string? logAlias = null)
public Http(ILog log, ITimeSet timeSet, Address address, string baseUrl, string? logAlias = null)
: this(log, timeSet, address, baseUrl, DoNothing, logAlias)
{
}
public Http(BaseLog log, ITimeSet timeSet, Address address, string baseUrl, Action<HttpClient> onClientCreated, string? logAlias = null)
public Http(ILog log, ITimeSet timeSet, Address address, string baseUrl, Action<HttpClient> onClientCreated, string? logAlias = null)
{
this.log = log;
this.timeSet = timeSet;

View File

@ -0,0 +1,64 @@
using KubernetesWorkflow;
using Logging;
namespace DistTestCore
{
public class PluginManager : IPluginActions
{
private readonly BaseLog log;
private readonly Configuration configuration;
private readonly string testNamespace;
private readonly WorkflowCreator workflowCreator;
private readonly ITimeSet timeSet;
private readonly List<IProjectPlugin> projectPlugins = new List<IProjectPlugin>();
public PluginManager(BaseLog log, Configuration configuration, ITimeSet timeSet, string testNamespace)
{
this.log = log;
this.configuration = configuration;
this.timeSet = timeSet;
this.testNamespace = testNamespace;
workflowCreator = new WorkflowCreator(log, configuration.GetK8sConfiguration(timeSet), testNamespace);
}
public IStartupWorkflow CreateWorkflow()
{
return workflowCreator.CreateWorkflow();
}
public ILog GetLog()
{
return log;
}
public ITimeSet GetTimeSet()
{
return timeSet;
}
public void InitializeAllPlugins()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var pluginTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => typeof(IProjectPlugin).IsAssignableFrom(t))).ToArray();
foreach (var pluginType in pluginTypes)
{
IPluginActions actions = this;
var plugin = (IProjectPlugin)Activator.CreateInstance(pluginType, args: actions)!;
projectPlugins.Add(plugin);
}
}
}
public interface IProjectPlugin
{
}
// probably seggregate this out.
public interface IPluginActions
{
IStartupWorkflow CreateWorkflow();
ILog GetLog();
ITimeSet GetTimeSet();
}
}

View File

@ -26,6 +26,14 @@ namespace DistTestCore
testStart = DateTime.UtcNow;
//CodexVersion = null;
// the plugin manager is starting to look like the testlifecycle, that's bad because they are not supposed to be doing the same things:
// pluginmanager should be useful for disttest-deployer-continuoustest, everyone!
// but testlifecycle should be a disttest specific user of the plugin manager.
// disttest requires a hook by which it can keep track of containers created?? (does it?) /namespace used? for the purpose of cleaning up.
//var pluginManager = new PluginManager(Log, configuration, timeSet, testNamespace);
//pluginManager.InitializeAllPlugins();
Log.WriteLogTag();
}

View File

@ -0,0 +1,55 @@
using System.Net.NetworkInformation;
using Utils;
namespace KubernetesWorkflow
{
internal enum RunnerLocation
{
ExternalToCluster,
InternalToCluster,
}
internal static class RunnerLocationUtils
{
private static RunnerLocation? knownLocation = null;
internal static RunnerLocation DetermineRunnerLocation(RunningContainer container)
{
if (knownLocation != null) return knownLocation.Value;
if (PingHost(container.Pod.PodInfo.Ip))
{
knownLocation = RunnerLocation.InternalToCluster;
}
if (PingHost(Format(container.ClusterExternalAddress)))
{
knownLocation = RunnerLocation.ExternalToCluster;
}
if (knownLocation == null) throw new Exception("Unable to determine location relative to kubernetes cluster.");
return knownLocation.Value;
}
private static string Format(Address host)
{
return host.Host
.Replace("http://", "")
.Replace("https://", "");
}
private static bool PingHost(string host)
{
try
{
using var pinger = new Ping();
PingReply reply = pinger.Send(host);
return reply.Status == IPStatus.Success;
}
catch (PingException)
{
}
return false;
}
}
}

View File

@ -43,6 +43,19 @@ namespace KubernetesWorkflow
[JsonIgnore]
public CrashWatcher? CrashWatcher { get; set; }
[JsonIgnore]
public Address Address
{
get
{
if (RunnerLocationUtils.DetermineRunnerLocation(this) == RunnerLocation.InternalToCluster)
{
return ClusterInternalAddress;
}
return ClusterExternalAddress;
}
}
}
public static class RunningContainersExtensions

View File

@ -5,6 +5,7 @@
private readonly List<object> configs = new List<object>();
public string? NameOverride { get; set; }
public bool CreateCrashWatcher { get; set; }
public void Add(object config)
{

View File

@ -3,7 +3,17 @@ using Utils;
namespace KubernetesWorkflow
{
public class StartupWorkflow
public interface IStartupWorkflow
{
RunningContainers Start(int numberOfContainers, Location location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig);
void Stop(RunningContainers runningContainers);
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines);
string ExecuteCommand(RunningContainer container, string command, params string[] args);
void DeleteAllResources();// !!! delete namespace then!?
void DeleteTestResources(); // !!! do not mention tests. what are we deleting?
}
public class StartupWorkflow : IStartupWorkflow
{
private readonly BaseLog log;
private readonly WorkflowNumberSource numberSource;
@ -26,18 +36,15 @@ namespace KubernetesWorkflow
return K8s(controller =>
{
var recipes = CreateRecipes(numberOfContainers, recipeFactory, startupConfig);
var runningPod = controller.BringOnline(recipes, location);
var containers = CreateContainers(runningPod, recipes, startupConfig);
return new RunningContainers(startupConfig, runningPod, CreateContainers(runningPod, recipes, startupConfig));
if (startupConfig.CreateCrashWatcher) CreateCrashWatchers(controller, containers);
return new RunningContainers(startupConfig, runningPod, containers);
});
}
public CrashWatcher CreateCrashWatcher(RunningContainer container)
{
return K8s(controller => controller.CreateCrashWatcher(container));
}
public void Stop(RunningContainers runningContainers)
{
K8s(controller =>
@ -78,6 +85,14 @@ namespace KubernetesWorkflow
});
}
private void CreateCrashWatchers(K8sController controller, RunningContainer[] runningContainers)
{
foreach (var container in runningContainers)
{
container.CrashWatcher = controller.CreateCrashWatcher(container);
}
}
private RunningContainer[] CreateContainers(RunningPod runningPod, ContainerRecipe[] recipes, StartupConfig startupConfig)
{
log.Debug();

View File

@ -19,7 +19,7 @@ namespace KubernetesWorkflow
this.testNamespace = testNamespace.ToLowerInvariant();
}
public StartupWorkflow CreateWorkflow()
public IStartupWorkflow CreateWorkflow()
{
var workflowNumberSource = new WorkflowNumberSource(numberSource.GetNextNumber(),
containerNumberSource);

View File

@ -1,8 +1,17 @@
using Utils;
using System.Diagnostics;
using Utils;
namespace Logging
{
public abstract class BaseLog
public interface ILog
{
void Log(string message);
void Debug(string message = "", int skipFrames = 0);
void Error(string message);
LogFile CreateSubfile(string ext = "log");
}
public abstract class BaseLog : ILog
{
private readonly NumberSource subfileNumberSource = new NumberSource(0);
private readonly bool debug;

View File

@ -5,25 +5,25 @@ namespace Logging
public class Stopwatch
{
private readonly DateTime start = DateTime.UtcNow;
private readonly BaseLog log;
private readonly ILog log;
private readonly string name;
private readonly bool debug;
private Stopwatch(BaseLog log, string name, bool debug)
private Stopwatch(ILog log, string name, bool debug)
{
this.log = log;
this.name = name;
this.debug = debug;
}
public static void Measure(BaseLog log, string name, Action action, bool debug = false)
public static void Measure(ILog log, string name, Action action, bool debug = false)
{
var sw = Begin(log, name, debug);
action();
sw.End();
}
public static T Measure<T>(BaseLog log, string name, Func<T> action, bool debug = false)
public static T Measure<T>(ILog log, string name, Func<T> action, bool debug = false)
{
var sw = Begin(log, name, debug);
var result = action();
@ -31,22 +31,22 @@ namespace Logging
return result;
}
public static Stopwatch Begin(BaseLog log)
public static Stopwatch Begin(ILog log)
{
return Begin(log, "");
}
public static Stopwatch Begin(BaseLog log, string name)
public static Stopwatch Begin(ILog log, string name)
{
return Begin(log, name, false);
}
public static Stopwatch Begin(BaseLog log, bool debug)
public static Stopwatch Begin(ILog log, bool debug)
{
return Begin(log, "", debug);
}
public static Stopwatch Begin(BaseLog log, string name, bool debug)
public static Stopwatch Begin(ILog log, string name, bool debug)
{
return new Stopwatch(log, name, debug);
}

View File

@ -14,7 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\CodexPlugin\CodexPlugin.csproj" />
<ProjectReference Include="..\ContinuousTests\ContinuousTests.csproj" />
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
</ItemGroup>