working transcript writer and reader
This commit is contained in:
parent
8064051f2f
commit
802b18e990
@ -0,0 +1,72 @@
|
||||
namespace OverwatchTranscript
|
||||
{
|
||||
[Serializable]
|
||||
public partial class OverwatchHeader
|
||||
{
|
||||
public OverwatchCodexHeader? CodexHeader { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public partial class OverwatchEvent
|
||||
{
|
||||
public OverwatchCodexEvent? CodexEvent { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class OverwatchCodexHeader
|
||||
{
|
||||
public int TotalNumberOfNodes { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class OverwatchCodexEvent
|
||||
{
|
||||
public NodeStartedEvent? NodeStarted { get; set; }
|
||||
public NodeStoppedEvent? NodeStopped { get; set; }
|
||||
public FileUploadedEvent? FileUploaded { get; set; }
|
||||
public FileDownloadedEvent? FileDownloaded { get; set; }
|
||||
public BlockReceivedEvent? BlockReceived { get; set; }
|
||||
}
|
||||
|
||||
#region Scenario Generated Events
|
||||
|
||||
[Serializable]
|
||||
public class NodeStartedEvent
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Image { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NodeStoppedEvent
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FileUploadedEvent
|
||||
{
|
||||
public ulong ByteSize { get; set; }
|
||||
public string Cid { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FileDownloadedEvent
|
||||
{
|
||||
public string Cid { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Codex Generated Events
|
||||
|
||||
[Serializable]
|
||||
public class BlockReceivedEvent
|
||||
{
|
||||
public string BlockAddress { get; set; } = string.Empty;
|
||||
public string PeerId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
<ProjectReference Include="..\..\Framework\NethereumWorkflow\NethereumWorkflow.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\Utils\Utils.csproj" />
|
||||
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
|
||||
<ProjectReference Include="..\..\Tools\OverwatchTranscript\OverwatchTranscript.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
103
Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs
Normal file
103
Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using OverwatchTranscript;
|
||||
|
||||
namespace FrameworkTests.OverwatchTranscript
|
||||
{
|
||||
[TestFixture]
|
||||
public class TranscriptTests
|
||||
{
|
||||
private const string TranscriptFilename = "testtranscript.json";
|
||||
private const string HeaderKey = "testHeader";
|
||||
private const string HeaderData = "abcdef";
|
||||
private const string EventData0 = "12345";
|
||||
private const string EventData1 = "678";
|
||||
private const string EventData2 = "90";
|
||||
private readonly DateTime t0 = DateTime.UtcNow;
|
||||
private readonly DateTime t1 = DateTime.UtcNow.AddMinutes(1);
|
||||
private readonly DateTime t2 = DateTime.UtcNow.AddMinutes(2);
|
||||
|
||||
[Test]
|
||||
public void WriteAndRun()
|
||||
{
|
||||
var workdir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
|
||||
WriteTranscript(workdir);
|
||||
ReadTranscript(workdir);
|
||||
|
||||
File.Delete(TranscriptFilename);
|
||||
}
|
||||
|
||||
private void WriteTranscript(string workdir)
|
||||
{
|
||||
var writer = new TranscriptWriter(workdir);
|
||||
|
||||
writer.AddHeader(HeaderKey, new TestHeader
|
||||
{
|
||||
HeaderData = HeaderData
|
||||
});
|
||||
|
||||
writer.Add(t0, new MyEvent
|
||||
{
|
||||
EventData = EventData0
|
||||
});
|
||||
writer.Add(t2, new MyEvent
|
||||
{
|
||||
EventData = EventData2
|
||||
});
|
||||
writer.Add(t1, new MyEvent
|
||||
{
|
||||
EventData = EventData1
|
||||
});
|
||||
|
||||
writer.Write(TranscriptFilename);
|
||||
}
|
||||
|
||||
private void ReadTranscript(string workdir)
|
||||
{
|
||||
var reader = new TranscriptReader(workdir, TranscriptFilename);
|
||||
|
||||
var header = reader.GetHeader<TestHeader>(HeaderKey);
|
||||
Assert.That(header.HeaderData, Is.EqualTo(HeaderData));
|
||||
|
||||
var events = new List<MyEvent>();
|
||||
reader.AddHandler<MyEvent>((utc, e) =>
|
||||
{
|
||||
e.CheckUtc = utc;
|
||||
events.Add(e);
|
||||
});
|
||||
|
||||
Assert.That(events.Count, Is.EqualTo(0));
|
||||
reader.Next();
|
||||
Assert.That(events.Count, Is.EqualTo(1));
|
||||
reader.Next();
|
||||
Assert.That(events.Count, Is.EqualTo(2));
|
||||
reader.Next();
|
||||
Assert.That(events.Count, Is.EqualTo(3));
|
||||
reader.Next();
|
||||
Assert.That(events.Count, Is.EqualTo(3));
|
||||
|
||||
Assert.That(events[0].CheckUtc, Is.EqualTo(t0));
|
||||
Assert.That(events[0].EventData, Is.EqualTo(EventData0));
|
||||
Assert.That(events[1].CheckUtc, Is.EqualTo(t1));
|
||||
Assert.That(events[1].EventData, Is.EqualTo(EventData1));
|
||||
Assert.That(events[2].CheckUtc, Is.EqualTo(t2));
|
||||
Assert.That(events[2].EventData, Is.EqualTo(EventData2));
|
||||
|
||||
reader.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestHeader
|
||||
{
|
||||
public string HeaderData { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class MyEvent
|
||||
{
|
||||
public string EventData { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTime CheckUtc { get; set; }
|
||||
}
|
||||
}
|
@ -1,76 +1,30 @@
|
||||
namespace OverwatchTranscript
|
||||
{
|
||||
[Serializable]
|
||||
public class Transcript
|
||||
public class OverwatchTranscript
|
||||
{
|
||||
public Header Header { get; set; } = new();
|
||||
public Event[] Events { get; set; } = Array.Empty<Event>();
|
||||
public OverwatchHeader Header { get; set; } = new();
|
||||
public OverwatchEvent[] Events { get; set; } = Array.Empty<OverwatchEvent>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Header
|
||||
public class OverwatchHeader
|
||||
{
|
||||
public int TotalNumberOfNodes { get; set; }
|
||||
public OverwatchHeaderEntry[] Entries { get; set; } = Array.Empty<OverwatchHeaderEntry>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Event
|
||||
public class OverwatchHeaderEntry
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class OverwatchEvent
|
||||
{
|
||||
public DateTime Utc { get; set; }
|
||||
public ScenarioFinishedEvent? ScenarioFinished { get; set; }
|
||||
public NodeStartedEvent? NodeStarted { get; set; }
|
||||
public NodeStoppedEvent? NodeStopped { get; set; }
|
||||
public FileUploadedEvent? FileUploaded { get; set; }
|
||||
public FileDownloadedEvent? FileDownloaded { get; set; }
|
||||
public BlockReceivedEvent? BlockReceived { get; set; }
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Payload { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#region Scenario Generated Events
|
||||
|
||||
[Serializable]
|
||||
public class ScenarioFinishedEvent
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Result { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NodeStartedEvent
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Image { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NodeStoppedEvent
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FileUploadedEvent
|
||||
{
|
||||
public ulong ByteSize { get; set; }
|
||||
public string Cid { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FileDownloadedEvent
|
||||
{
|
||||
public string Cid { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Codex Generated Events
|
||||
|
||||
[Serializable]
|
||||
public class BlockReceivedEvent
|
||||
{
|
||||
public string BlockAddress { get; set; } = string.Empty;
|
||||
public string PeerId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
8
Tools/OverwatchTranscript/TranscriptConstants.cs
Normal file
8
Tools/OverwatchTranscript/TranscriptConstants.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace OverwatchTranscript
|
||||
{
|
||||
public static class TranscriptConstants
|
||||
{
|
||||
public const string TranscriptFilename = "transcript.json";
|
||||
public const string ArtifactFolderName = "artifacts";
|
||||
}
|
||||
}
|
92
Tools/OverwatchTranscript/TranscriptReader.cs
Normal file
92
Tools/OverwatchTranscript/TranscriptReader.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace OverwatchTranscript
|
||||
{
|
||||
public class TranscriptReader
|
||||
{
|
||||
private readonly string transcriptFile;
|
||||
private readonly string artifactsFolder;
|
||||
private readonly Dictionary<string, Action<DateTime, string>> handlers = new Dictionary<string, Action<DateTime, string>>();
|
||||
private readonly string workingDir;
|
||||
private OverwatchTranscript model = null!;
|
||||
private int eventIndex = 0;
|
||||
private bool closed;
|
||||
|
||||
public TranscriptReader(string workingDir, string inputFilename)
|
||||
{
|
||||
closed = false;
|
||||
this.workingDir = workingDir;
|
||||
transcriptFile = Path.Combine(workingDir, TranscriptConstants.TranscriptFilename);
|
||||
artifactsFolder = Path.Combine(workingDir, TranscriptConstants.ArtifactFolderName);
|
||||
|
||||
if (!Directory.Exists(workingDir)) Directory.CreateDirectory(workingDir);
|
||||
if (File.Exists(transcriptFile) || Directory.Exists(artifactsFolder)) throw new Exception("workingdir not clean");
|
||||
|
||||
LoadModel(inputFilename);
|
||||
}
|
||||
|
||||
public T GetHeader<T>(string key)
|
||||
{
|
||||
CheckClosed();
|
||||
var value = model.Header.Entries.First(e => e.Key == key).Value;
|
||||
return JsonConvert.DeserializeObject<T>(value)!;
|
||||
}
|
||||
|
||||
public void AddHandler<T>(Action<DateTime, T> handler)
|
||||
{
|
||||
CheckClosed();
|
||||
var typeName = typeof(T).FullName;
|
||||
if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload");
|
||||
|
||||
handlers.Add(typeName, (utc, s) =>
|
||||
{
|
||||
handler(utc, JsonConvert.DeserializeObject<T>(s)!);
|
||||
});
|
||||
}
|
||||
|
||||
public void Next()
|
||||
{
|
||||
CheckClosed();
|
||||
if (eventIndex >= model.Events.Length) return;
|
||||
|
||||
var @event = model.Events[eventIndex];
|
||||
eventIndex++;
|
||||
|
||||
PlayEvent(@event);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
CheckClosed();
|
||||
Directory.Delete(workingDir, true);
|
||||
closed = true;
|
||||
}
|
||||
|
||||
private void PlayEvent(OverwatchEvent @event)
|
||||
{
|
||||
if (!handlers.ContainsKey(@event.Type)) return;
|
||||
var handler = handlers[@event.Type];
|
||||
|
||||
handler(@event.Utc, @event.Payload);
|
||||
}
|
||||
|
||||
private void LoadModel(string inputFilename)
|
||||
{
|
||||
ZipFile.ExtractToDirectory(inputFilename, workingDir);
|
||||
|
||||
if (!File.Exists(transcriptFile))
|
||||
{
|
||||
closed = true;
|
||||
throw new Exception("Is not a transcript file. Unzipped to: " + workingDir);
|
||||
}
|
||||
|
||||
model = JsonConvert.DeserializeObject<OverwatchTranscript>(File.ReadAllText(transcriptFile))!;
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again.");
|
||||
}
|
||||
}
|
||||
}
|
98
Tools/OverwatchTranscript/TranscriptWriter.cs
Normal file
98
Tools/OverwatchTranscript/TranscriptWriter.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace OverwatchTranscript
|
||||
{
|
||||
public interface ITranscriptWriter
|
||||
{
|
||||
void AddHeader(string key, object value);
|
||||
void Add(DateTime utc, object payload);
|
||||
void IncludeArtifact(string filePath);
|
||||
void Write(string outputFilename);
|
||||
}
|
||||
|
||||
public class TranscriptWriter : ITranscriptWriter
|
||||
{
|
||||
private readonly string transcriptFile;
|
||||
private readonly string artifactsFolder;
|
||||
private readonly Dictionary<string, string> header = new Dictionary<string, string>();
|
||||
private readonly SortedList<DateTime, OverwatchEvent> buffer = new SortedList<DateTime, OverwatchEvent>();
|
||||
private readonly string workingDir;
|
||||
private bool closed;
|
||||
|
||||
public TranscriptWriter(string workingDir)
|
||||
{
|
||||
closed = false;
|
||||
this.workingDir = workingDir;
|
||||
transcriptFile = Path.Combine(workingDir, TranscriptConstants.TranscriptFilename);
|
||||
artifactsFolder = Path.Combine(workingDir, TranscriptConstants.ArtifactFolderName);
|
||||
|
||||
if (!Directory.Exists(workingDir)) Directory.CreateDirectory(workingDir);
|
||||
if (File.Exists(transcriptFile) || Directory.Exists(artifactsFolder)) throw new Exception("workingdir not clean");
|
||||
}
|
||||
|
||||
public void Add(DateTime utc, object payload)
|
||||
{
|
||||
CheckClosed();
|
||||
var typeName = payload.GetType().FullName;
|
||||
if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload");
|
||||
|
||||
buffer.Add(utc, new OverwatchEvent
|
||||
{
|
||||
Utc = utc,
|
||||
Type = typeName,
|
||||
Payload = JsonConvert.SerializeObject(payload)
|
||||
});
|
||||
}
|
||||
|
||||
public void AddHeader(string key, object value)
|
||||
{
|
||||
CheckClosed();
|
||||
header.Add(key, JsonConvert.SerializeObject(value));
|
||||
}
|
||||
|
||||
public void IncludeArtifact(string filePath)
|
||||
{
|
||||
CheckClosed();
|
||||
if (!File.Exists(filePath)) throw new Exception("File not found: " + filePath);
|
||||
var name = Path.GetFileName(filePath);
|
||||
File.Copy(filePath, Path.Combine(artifactsFolder, name), overwrite: false);
|
||||
}
|
||||
|
||||
public void Write(string outputFilename)
|
||||
{
|
||||
CheckClosed();
|
||||
closed = true;
|
||||
|
||||
var model = new OverwatchTranscript
|
||||
{
|
||||
Header = new OverwatchHeader
|
||||
{
|
||||
Entries = header.Select(h =>
|
||||
{
|
||||
return new OverwatchHeaderEntry
|
||||
{
|
||||
Key = h.Key,
|
||||
Value = h.Value
|
||||
};
|
||||
}).ToArray()
|
||||
},
|
||||
Events = buffer.Values.ToArray()
|
||||
};
|
||||
|
||||
header.Clear();
|
||||
buffer.Clear();
|
||||
|
||||
File.WriteAllText(transcriptFile, JsonConvert.SerializeObject(model, Formatting.Indented));
|
||||
|
||||
ZipFile.CreateFromDirectory(workingDir, outputFilename);
|
||||
|
||||
Directory.Delete(workingDir, true);
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again.");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user