Skip to content

Commit

Permalink
Multinode Test Visualizer
Browse files Browse the repository at this point in the history
VisualizerPersistenTestRunStore creates a html file with a timeline
visual showing the node events.

This file sits alongside the json file has has the same name
(excluding file extension)
  • Loading branch information
GraemeBradbury committed Sep 14, 2015
1 parent 0744075 commit 7706bb2
Show file tree
Hide file tree
Showing 14 changed files with 759 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,19 @@
<ItemGroup>
<Compile Include="ExitCodeContainer.cs" />
<Compile Include="NodeTest.cs" />
<Compile Include="Persistence\EnumerableExtensions.cs" />
<Compile Include="Persistence\FileNameGenerator.cs" />
<Compile Include="Persistence\IPersistentTestRunStore.cs" />
<Compile Include="Persistence\IRetrievableTestRunStore.cs" />
<Compile Include="Persistence\JsonPersistentTestRunStore.cs" />
<Compile Include="Persistence\TimelineItem.cs" />
<Compile Include="Persistence\VisualizerRuntimeTemplate.Tree.cs" />
<Compile Include="Persistence\VisualizerPersistentTestRunStore.cs" />
<Compile Include="Persistence\VisualizerRuntimeTemplate.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>VisualizerRuntimeTemplate.tt</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sinks\ConsoleMessageSinkActor.cs" />
<Compile Include="Sinks\FileSystemMessageSinkActor.cs" />
Expand All @@ -69,6 +80,15 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<Content Include="Persistence\VisualizerRuntimeTemplate.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>VisualizerRuntimeTemplate.cs</LastGenOutput>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="EnumerableExtensions.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System.Collections.Generic;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
public static class EnumerableExtensions
{
public static IEnumerable<T> Concat<T>(this IEnumerable<T> source, T item)
{
foreach (var cur in source)
{
yield return cur;
}
yield return item;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// -----------------------------------------------------------------------
// <copyright file="FileNameGenerator.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
public class FileNameGenerator
{
public static string GenerateFileName(string assemblyName, string fileExtension)
{
return GenerateFileName(assemblyName, fileExtension, DateTime.UtcNow);
}

public static string GenerateFileName(string assemblyName, string fileExtension, DateTime utcNow)
{
return string.Format("{0}-{1}{2}", assemblyName.Replace(".dll", ""), utcNow.Ticks, fileExtension);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@
namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
/// <summary>
/// Persistent store for saving and retrieving <see cref="TestRunTree"/> instances
/// Persistent store for saving <see cref="TestRunTree"/> instances
/// from disk.
/// </summary>
public interface IPersistentTestRunStore
{
bool SaveTestRun(string filePath, TestRunTree data);

bool TestRunExists(string filePath);

TestRunTree FetchTestRun(string filePath);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="IRetrievableTestRunStore.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using Akka.MultiNodeTestRunner.Shared.Reporting;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
/// <summary>
/// Persistent store for retreiving <see cref="TestRunTree" /> instances
/// from disk.
/// </summary>
public interface IRetrievableTestRunStore :IPersistentTestRunStore
{
bool TestRunExists(string filePath);

TestRunTree FetchTestRun(string filePath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
/// <summary>
/// JavaScript Object Notation (JSON) implementation of the <see cref="IPersistentTestRunStore"/>
/// JavaScript Object Notation (JSON) implementation of the <see cref="IRetrievableTestRunStore"/>
/// </summary>
public class JsonPersistentTestRunStore : IPersistentTestRunStore
public class JsonPersistentTestRunStore : IRetrievableTestRunStore
{
//Internal version of the contract resolver
private class AkkaContractResolver : DefaultContractResolver
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// -----------------------------------------------------------------------
// <copyright file="TimelineItem.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
public class TimelineItem
{
private static readonly string eventFormat = "{{ className:'{0}', content:'{1}', start:'{2}', group:{3} }}";

private static readonly string[] CssClasses =
{
"vis-item-one",
"vis-item-two",
"vis-item-three",
"vis-item-four",
"vis-item-five",
"vis-item-six",
"vis-item-seven",
"vis-item-eight",
"vis-item-nine",
"vis-item-ten",
"vis-item-eleven",
"vis-item-twelve",
"vis-item-thirteen",
"vis-item-fourteen",
"vis-item-fifteen"
};

public string Classname { get; private set; }

public string Content { get; private set; }

public DateTime Start { get; private set; }

public int GroupId { get; private set; }

public string ToJavascriptString()
{
return string.Format(eventFormat, Classname, Content, Start.ToString("o"), GroupId);
}

public static TimelineItem CreateSpecMessage(string content, int groupId, long startTimeStamp)
{
return new TimelineItem
{
Classname = "timeline-message",
Content = content,
Start = new DateTime(startTimeStamp),
GroupId = groupId
};
}

public static TimelineItem CreateNodeFact(string content, int groupId, long startTimeStamp)
{
return new TimelineItem
{
Classname = CssClasses[startTimeStamp % 15],
Content = content,
Start = new DateTime(startTimeStamp),
GroupId = groupId
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// -----------------------------------------------------------------------
// <copyright file="VisualizerPersistentTestRunStore.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System.IO;

using Akka.MultiNodeTestRunner.Shared.Reporting;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
/// <summary>
/// Stores test run as a html page.
/// </summary>
public class VisualizerPersistentTestRunStore : IPersistentTestRunStore
{
public bool SaveTestRun(string filePath, TestRunTree data)
{
var template = new VisualizerRuntimeTemplate { Tree = data };
var content = template.TransformText();
var fullPath = Path.GetFullPath(filePath);
File.WriteAllText(fullPath, content);

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// -----------------------------------------------------------------------
// <copyright file="VisualizerRuntimeTemplate.Tree.cs" company="Akka.NET Project">
// Copyright (C) 2013-2015 Akka.NET project <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using Akka.MultiNodeTestRunner.Shared.Reporting;

namespace Akka.MultiNodeTestRunner.Shared.Persistence
{
partial class VisualizerRuntimeTemplate
{
private TestRunTree _tree;
public string Prefix { get; private set; }

public TestRunTree Tree
{
get { return _tree; }
set
{
_tree = value;
Prefix = LongestCommonPrefix(value.Specs
.Select(s => s.FactName)
.ToArray());
}
}

public string BuildSpecificationId(FactData spec)
{
return spec.FactName.Replace(".", "_");
}

public string BuildTimelineItem(FactData spec)
{
var messages =
spec.RunnerMessages
.Select(m =>
TimelineItem.CreateSpecMessage(m.Message, m.NodeIndex, m.TimeStamp));

var facts =
spec.NodeFacts
.SelectMany(m =>
m.Value.EventStream
.Select(e =>
TimelineItem.CreateNodeFact(e.Message, e.NodeIndex, e.TimeStamp)));


var itemStrings = messages.Concat(facts)
.Select(i => i.ToJavascriptString());

return string.Join(",\r\n", itemStrings);
}

public string BuildGroupItems(FactData spec)
{
var groups = spec.NodeFacts
.Select(nf =>
string.Format("{{ id:{0}, content:'Node {0}' }}", nf.Value.NodeIndex))
.Concat(@"{ id:-1, content:'Misc' }");

return string.Join(",\r\n", groups);
}

private static string LongestCommonPrefix(IReadOnlyList<string> strings)
{
if (strings == null || strings.Count == 0)
{
return string.Empty;
}

var commonPrefix = strings[0];

for (var i = 1; i < strings.Count; i++)
{
var j = 0;
for (; j < commonPrefix.Length && j < strings[i].Length; j++)
{
if (commonPrefix[j] != strings[i][j])
{
commonPrefix = commonPrefix.Substring(0, j);
break;
}
}

if (j == strings[i].Length)
{
commonPrefix = strings[i];
}
}

return commonPrefix;
}
}
}
Loading

0 comments on commit 7706bb2

Please sign in to comment.