#region Copyright notice and license | |
// Protocol Buffers - Google's data interchange format | |
// Copyright 2008 Google Inc. All rights reserved. | |
// http://github.com/jskeet/dotnet-protobufs/ | |
// Original C++/Java/Python code: | |
// http://code.google.com/p/protobuf/ | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are | |
// met: | |
// | |
// * Redistributions of source code must retain the above copyright | |
// notice, this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above | |
// copyright notice, this list of conditions and the following disclaimer | |
// in the documentation and/or other materials provided with the | |
// distribution. | |
// * Neither the name of Google Inc. nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
#endregion | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Runtime.Serialization.Json; | |
using System.Text; | |
using System.Threading; | |
using System.Xml; | |
using Google.ProtocolBuffers.Serialization; | |
using Google.ProtocolBuffers.TestProtos; | |
namespace Google.ProtocolBuffers.ProtoBench | |
{ | |
/// <summary> | |
/// Simple benchmarking of arbitrary messages. | |
/// </summary> | |
public sealed class Program | |
{ | |
private static TimeSpan MinSampleTime = TimeSpan.FromSeconds(2); | |
private static TimeSpan TargetTime = TimeSpan.FromSeconds(30); | |
private static bool Verbose = false, FastTest = false, OtherFormats = false; | |
// Avoid a .NET 3.5 dependency | |
private delegate void Action(); | |
private delegate void BenchmarkTest(string name, long dataSize, Action action); | |
private static BenchmarkTest RunBenchmark; | |
private static string _logFile; | |
static void WriteLine(string format, params object[] arg) | |
{ | |
if (arg.Length > 0) format = String.Format(format, arg); | |
Console.Out.WriteLine(format); | |
if (!String.IsNullOrEmpty(_logFile)) | |
File.AppendAllText(_logFile, format + Environment.NewLine); | |
} | |
[STAThread] | |
public static int Main(string[] args) | |
{ | |
List<string> temp = new List<string>(args); | |
Verbose = temp.Remove("/verbose") || temp.Remove("-verbose"); | |
OtherFormats = temp.Remove("/formats") || temp.Remove("-formats"); | |
foreach (string arg in temp) | |
{ | |
if (arg.StartsWith("/log:", StringComparison.OrdinalIgnoreCase) || arg.StartsWith("-log:", StringComparison.OrdinalIgnoreCase)) | |
{ | |
_logFile = arg.Substring(5); | |
if (!String.IsNullOrEmpty(_logFile)) | |
File.AppendAllText(_logFile, Environment.NewLine + "Started benchmarks at " + DateTime.Now + Environment.NewLine); | |
temp.Remove(arg); | |
break; | |
} | |
} | |
if (true == (FastTest = (temp.Remove("/fast") || temp.Remove("-fast")))) | |
{ | |
TargetTime = TimeSpan.FromSeconds(10); | |
} | |
RunBenchmark = BenchmarkV1; | |
if (temp.Remove("/v2") || temp.Remove("-v2")) | |
{ | |
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; | |
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); | |
RunBenchmark = BenchmarkV2; | |
} | |
if (temp.Remove("/all") || temp.Remove("-all")) | |
{ | |
if (FastTest) | |
{ | |
TargetTime = TimeSpan.FromSeconds(5); | |
} | |
foreach (KeyValuePair<string, string> item in MakeTests()) | |
{ | |
temp.Add(item.Key); | |
temp.Add(item.Value); | |
} | |
} | |
args = temp.ToArray(); | |
if (args.Length < 2 || (args.Length%2) != 0) | |
{ | |
Console.Error.WriteLine("Usage: ProtoBench [/fast] <descriptor type name> <input data>"); | |
Console.Error.WriteLine("The descriptor type name is the fully-qualified message name,"); | |
Console.Error.WriteLine( | |
"including assembly - e.g. Google.ProtocolBuffers.BenchmarkProtos.Message1,ProtoBench"); | |
Console.Error.WriteLine("(You can specify multiple pairs of descriptor type name and input data.)"); | |
return 1; | |
} | |
bool success = true; | |
for (int i = 0; i < args.Length; i += 2) | |
{ | |
success &= RunTest(args[i], args[i + 1], null); | |
} | |
return success ? 0 : 1; | |
} | |
/// <summary> | |
/// Runs a single test. Error messages are displayed to Console.Error, and the return value indicates | |
/// general success/failure. | |
/// </summary> | |
public static bool RunTest(string typeName, string file, byte[] inputData) | |
{ | |
WriteLine("Benchmarking {0} with file {1}", typeName, file); | |
IMessage defaultMessage; | |
try | |
{ | |
defaultMessage = MessageUtil.GetDefaultMessage(typeName); | |
} | |
catch (ArgumentException e) | |
{ | |
Console.Error.WriteLine(e.Message); | |
return false; | |
} | |
try | |
{ | |
ExtensionRegistry registry = ExtensionRegistry.Empty; | |
inputData = inputData ?? File.ReadAllBytes(file); | |
MemoryStream inputStream = new MemoryStream(inputData); | |
ByteString inputString = ByteString.CopyFrom(inputData); | |
IMessage sampleMessage = | |
defaultMessage.WeakCreateBuilderForType().WeakMergeFrom(inputString, registry).WeakBuild(); | |
IDictionary<string, object> dictionary = null; | |
byte[] jsonBytes = null, xmlBytes = null; /*no pun intended, well... maybe for xml*/ | |
if (OtherFormats) | |
{ | |
using (MemoryStream temp = new MemoryStream()) | |
{ | |
XmlFormatWriter.CreateInstance(temp).WriteMessage(sampleMessage); | |
xmlBytes = temp.ToArray(); | |
} | |
using (MemoryStream temp = new MemoryStream()) | |
{ | |
JsonFormatWriter.CreateInstance(temp).WriteMessage(sampleMessage); | |
jsonBytes = temp.ToArray(); | |
} | |
dictionary = new Dictionary<string, object>(StringComparer.Ordinal); | |
new DictionaryWriter(dictionary).WriteMessage(sampleMessage); | |
} | |
//Serializers | |
if (!FastTest) | |
{ | |
RunBenchmark("Serialize to byte string", inputData.Length, () => sampleMessage.ToByteString()); | |
} | |
RunBenchmark("Serialize to byte array", inputData.Length, () => sampleMessage.ToByteArray()); | |
if (!FastTest) | |
{ | |
RunBenchmark("Serialize to memory stream", inputData.Length, | |
() => sampleMessage.WriteTo(new MemoryStream())); | |
} | |
if (OtherFormats) | |
{ | |
RunBenchmark("Serialize to xml", xmlBytes.Length, | |
() => | |
{ | |
XmlFormatWriter.CreateInstance(new MemoryStream(), Encoding.UTF8).WriteMessage(sampleMessage); | |
}); | |
RunBenchmark("Serialize to json", jsonBytes.Length, | |
() => { JsonFormatWriter.CreateInstance().WriteMessage(sampleMessage); }); | |
RunBenchmark("Serialize to json via xml", jsonBytes.Length, | |
() => | |
XmlFormatWriter.CreateInstance( | |
JsonReaderWriterFactory.CreateJsonWriter(new MemoryStream(), Encoding.UTF8)) | |
.SetOptions(XmlWriterOptions.OutputJsonTypes) | |
.WriteMessage(sampleMessage) | |
); | |
RunBenchmark("Serialize to dictionary", sampleMessage.SerializedSize, | |
() => new DictionaryWriter().WriteMessage(sampleMessage)); | |
} | |
//Deserializers | |
if (!FastTest) | |
{ | |
RunBenchmark("Deserialize from byte string", inputData.Length, | |
() => defaultMessage.WeakCreateBuilderForType() | |
.WeakMergeFrom(inputString, registry) | |
.WeakBuild() | |
); | |
} | |
RunBenchmark("Deserialize from byte array", inputData.Length, | |
() => defaultMessage.WeakCreateBuilderForType() | |
.WeakMergeFrom(CodedInputStream.CreateInstance(inputData), registry) | |
.WeakBuild() | |
); | |
if (!FastTest) | |
{ | |
RunBenchmark("Deserialize from memory stream", inputData.Length, | |
() => | |
{ | |
inputStream.Position = 0; | |
defaultMessage.WeakCreateBuilderForType().WeakMergeFrom( | |
CodedInputStream.CreateInstance(inputStream), registry) | |
.WeakBuild(); | |
}); | |
} | |
if (OtherFormats) | |
{ | |
RunBenchmark("Deserialize from xml", xmlBytes.Length, | |
() => | |
XmlFormatReader.CreateInstance(xmlBytes).Merge( | |
defaultMessage.WeakCreateBuilderForType()).WeakBuild()); | |
RunBenchmark("Deserialize from json", jsonBytes.Length, | |
() => | |
JsonFormatReader.CreateInstance(jsonBytes).Merge( | |
defaultMessage.WeakCreateBuilderForType()).WeakBuild()); | |
RunBenchmark("Deserialize from json via xml", jsonBytes.Length, | |
() => | |
XmlFormatReader.CreateInstance(JsonReaderWriterFactory.CreateJsonReader(jsonBytes, XmlDictionaryReaderQuotas.Max)) | |
.SetOptions(XmlReaderOptions.ReadNestedArrays).Merge( | |
defaultMessage.WeakCreateBuilderForType()).WeakBuild()); | |
RunBenchmark("Deserialize from dictionary", sampleMessage.SerializedSize, | |
() => | |
new DictionaryReader(dictionary).Merge(defaultMessage.WeakCreateBuilderForType()). | |
WeakBuild()); | |
} | |
WriteLine(String.Empty); | |
return true; | |
} | |
catch (Exception e) | |
{ | |
Console.Error.WriteLine("Error: {0}", e.Message); | |
Console.Error.WriteLine(); | |
Console.Error.WriteLine("Detailed exception information: {0}", e); | |
return false; | |
} | |
} | |
private static void BenchmarkV2(string name, long dataSize, Action action) | |
{ | |
Thread.BeginThreadAffinity(); | |
TimeSpan elapsed = TimeSpan.Zero; | |
long runs = 0; | |
long totalCount = 0; | |
double best = double.MinValue, worst = double.MaxValue; | |
action(); | |
// Run it progressively more times until we've got a reasonable sample | |
int iterations = 100; | |
elapsed = TimeAction(action, iterations); | |
while (elapsed.TotalMilliseconds < 1000) | |
{ | |
elapsed += TimeAction(action, iterations); | |
iterations *= 2; | |
} | |
TimeSpan target = TimeSpan.FromSeconds(1); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int) ((target.Ticks*iterations)/(double) elapsed.Ticks); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int) ((target.Ticks*iterations)/(double) elapsed.Ticks); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int) ((target.Ticks*iterations)/(double) elapsed.Ticks); | |
double first = (iterations*dataSize)/(elapsed.TotalSeconds*1024*1024); | |
if (Verbose) | |
{ | |
WriteLine("Round ---: Count = {1,6}, Bps = {2,8:f3}", 0, iterations, first); | |
} | |
elapsed = TimeSpan.Zero; | |
int max = (int) TargetTime.TotalSeconds; | |
while (runs < max) | |
{ | |
TimeSpan cycle = TimeAction(action, iterations); | |
// Accumulate and scale for next cycle. | |
double bps = (iterations*dataSize)/(cycle.TotalSeconds*1024*1024); | |
if (Verbose) | |
{ | |
WriteLine("Round {1,3}: Count = {2,6}, Bps = {3,8:f3}", | |
0, runs, iterations, bps); | |
} | |
best = Math.Max(best, bps); | |
worst = Math.Min(worst, bps); | |
runs++; | |
elapsed += cycle; | |
totalCount += iterations; | |
iterations = (int) ((target.Ticks*totalCount)/(double) elapsed.Ticks); | |
} | |
Thread.EndThreadAffinity(); | |
WriteLine( | |
"{1}: averages {2} per {3:f3}s for {4} runs; avg: {5:f3}mbps; best: {6:f3}mbps; worst: {7:f3}mbps", | |
0, name, totalCount/runs, elapsed.TotalSeconds/runs, runs, | |
(totalCount*dataSize)/(elapsed.TotalSeconds*1024*1024), best, worst); | |
} | |
private static void BenchmarkV1(string name, long dataSize, Action action) | |
{ | |
// Make sure it's JITted | |
action(); | |
// Run it progressively more times until we've got a reasonable sample | |
int iterations = 1; | |
TimeSpan elapsed = TimeAction(action, iterations); | |
while (elapsed < MinSampleTime) | |
{ | |
iterations *= 2; | |
elapsed = TimeAction(action, iterations); | |
} | |
// Upscale the sample to the target time. Do this in floating point arithmetic | |
// to avoid overflow issues. | |
iterations = (int) ((TargetTime.Ticks/(double) elapsed.Ticks)*iterations); | |
elapsed = TimeAction(action, iterations); | |
WriteLine("{0}: {1} iterations in {2:f3}s; {3:f3}MB/s", | |
name, iterations, elapsed.TotalSeconds, | |
(iterations*dataSize)/(elapsed.TotalSeconds*1024*1024)); | |
} | |
private static TimeSpan TimeAction(Action action, int iterations) | |
{ | |
GC.Collect(); | |
GC.GetTotalMemory(true); | |
GC.WaitForPendingFinalizers(); | |
Stopwatch sw = Stopwatch.StartNew(); | |
for (int i = 0; i < iterations; i++) | |
{ | |
action(); | |
} | |
sw.Stop(); | |
return sw.Elapsed; | |
} | |
private static IEnumerable<KeyValuePair<string, string>> MakeTests() | |
{ | |
//Aggregate Tests | |
yield return MakeWorkItem("all-types", MakeTestAllTypes()); | |
yield return MakeWorkItem("repeated-100", MakeRepeatedTestAllTypes(100)); | |
yield return MakeWorkItem("packed-100", MakeTestPackedTypes(100)); | |
//Discrete Tests | |
foreach (KeyValuePair<string, Action<TestAllTypes.Builder>> item in MakeTestAllTypes()) | |
{ | |
yield return MakeWorkItem(item.Key, new[] {item}); | |
} | |
foreach (KeyValuePair<string, Action<TestAllTypes.Builder>> item in MakeRepeatedTestAllTypes(100)) | |
{ | |
yield return MakeWorkItem(item.Key, new[] {item}); | |
} | |
foreach (KeyValuePair<string, Action<TestPackedTypes.Builder>> item in MakeTestPackedTypes(100)) | |
{ | |
yield return MakeWorkItem(item.Key, new[] {item}); | |
} | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestAllTypes.Builder>>> MakeTestAllTypes() | |
{ | |
// Many of the raw type serializers below perform poorly due to the numerous fields defined | |
// in TestAllTypes. | |
//single values | |
yield return MakeItem<TestAllTypes.Builder>("int32", 1, x => x.SetOptionalInt32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("int64", 1, x => x.SetOptionalInt64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("uint32", 1, x => x.SetOptionalUint32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("uint64", 1, x => x.SetOptionalUint64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sint32", 1, x => x.SetOptionalSint32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sint64", 1, x => x.SetOptionalSint64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("fixed32", 1, x => x.SetOptionalFixed32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("fixed64", 1, x => x.SetOptionalFixed64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sfixed32", 1, x => x.SetOptionalSfixed32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sfixed64", 1, x => x.SetOptionalSfixed64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("float", 1, x => x.SetOptionalFloat(1001.1001f)); | |
yield return MakeItem<TestAllTypes.Builder>("double", 1, x => x.SetOptionalDouble(1001.1001)); | |
yield return MakeItem<TestAllTypes.Builder>("bool", 1, x => x.SetOptionalBool(true)); | |
yield return MakeItem<TestAllTypes.Builder>("string", 1, x => x.SetOptionalString("this is a string value")) | |
; | |
yield return | |
MakeItem<TestAllTypes.Builder>("bytes", 1, | |
x => | |
x.SetOptionalBytes(ByteString.CopyFromUtf8("this is an array of bytes"))) | |
; | |
yield return | |
MakeItem<TestAllTypes.Builder>("group", 1, | |
x => | |
x.SetOptionalGroup( | |
new TestAllTypes.Types.OptionalGroup.Builder().SetA(1001))); | |
yield return | |
MakeItem<TestAllTypes.Builder>("message", 1, | |
x => | |
x.SetOptionalNestedMessage( | |
new TestAllTypes.Types.NestedMessage.Builder().SetBb(1001))); | |
yield return | |
MakeItem<TestAllTypes.Builder>("enum", 1, | |
x => x.SetOptionalNestedEnum(TestAllTypes.Types.NestedEnum.FOO)); | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestAllTypes.Builder>>> MakeRepeatedTestAllTypes(int size) | |
{ | |
//repeated values | |
yield return MakeItem<TestAllTypes.Builder>("repeated-int32", size, x => x.AddRepeatedInt32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-int64", size, x => x.AddRepeatedInt64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-uint32", size, x => x.AddRepeatedUint32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-uint64", size, x => x.AddRepeatedUint64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sint32", size, x => x.AddRepeatedSint32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sint64", size, x => x.AddRepeatedSint64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-fixed32", size, x => x.AddRepeatedFixed32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-fixed64", size, x => x.AddRepeatedFixed64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sfixed32", size, x => x.AddRepeatedSfixed32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sfixed64", size, x => x.AddRepeatedSfixed64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-float", size, x => x.AddRepeatedFloat(1001.1001f)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-double", size, x => x.AddRepeatedDouble(1001.1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-bool", size, x => x.AddRepeatedBool(true)); | |
yield return | |
MakeItem<TestAllTypes.Builder>("repeated-string", size, | |
x => x.AddRepeatedString("this is a string value")); | |
yield return | |
MakeItem<TestAllTypes.Builder>("repeated-bytes", size, | |
x => | |
x.AddRepeatedBytes(ByteString.CopyFromUtf8("this is an array of bytes"))) | |
; | |
yield return | |
MakeItem<TestAllTypes.Builder>("repeated-group", size, | |
x => | |
x.AddRepeatedGroup( | |
new TestAllTypes.Types.RepeatedGroup.Builder().SetA(1001))); | |
yield return | |
MakeItem<TestAllTypes.Builder>("repeated-message", size, | |
x => | |
x.AddRepeatedNestedMessage( | |
new TestAllTypes.Types.NestedMessage.Builder().SetBb(1001))); | |
yield return | |
MakeItem<TestAllTypes.Builder>("repeated-enum", size, | |
x => x.AddRepeatedNestedEnum(TestAllTypes.Types.NestedEnum.FOO)); | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestPackedTypes.Builder>>> MakeTestPackedTypes(int size) | |
{ | |
//packed values | |
yield return MakeItem<TestPackedTypes.Builder>("packed-int32", size, x => x.AddPackedInt32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-int64", size, x => x.AddPackedInt64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-uint32", size, x => x.AddPackedUint32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-uint64", size, x => x.AddPackedUint64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sint32", size, x => x.AddPackedSint32(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sint64", size, x => x.AddPackedSint64(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-fixed32", size, x => x.AddPackedFixed32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-fixed64", size, x => x.AddPackedFixed64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sfixed32", size, x => x.AddPackedSfixed32(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sfixed64", size, x => x.AddPackedSfixed64(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-float", size, x => x.AddPackedFloat(1001.1001f)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-double", size, x => x.AddPackedDouble(1001.1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-bool", size, x => x.AddPackedBool(true)); | |
yield return | |
MakeItem<TestPackedTypes.Builder>("packed-enum", size, x => x.AddPackedEnum(ForeignEnum.FOREIGN_FOO)); | |
} | |
private static KeyValuePair<string, Action<T>> MakeItem<T>(string name, int repeated, Action<T> build) | |
where T : IBuilderLite, new() | |
{ | |
if (repeated == 1) | |
{ | |
return new KeyValuePair<string, Action<T>>(name, build); | |
} | |
return new KeyValuePair<string, Action<T>>( | |
String.Format("{0}[{1}]", name, repeated), | |
x => | |
{ | |
for (int i = 0; i < repeated; i++) | |
{ | |
build(x); | |
} | |
} | |
); | |
} | |
private static KeyValuePair<string, string> MakeWorkItem<T>(string name, | |
IEnumerable<KeyValuePair<string, Action<T>>> | |
builders) where T : IBuilderLite, new() | |
{ | |
T builder = new T(); | |
foreach (KeyValuePair<string, Action<T>> item in builders) | |
{ | |
item.Value(builder); | |
} | |
IMessageLite msg = builder.WeakBuild(); | |
string fname = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "unittest_" + name + ".dat"); | |
File.WriteAllBytes(fname, msg.ToByteArray()); | |
return | |
new KeyValuePair<string, string>( | |
String.Format("{0},{1}", msg.GetType().FullName, msg.GetType().Assembly.GetName().Name), fname); | |
} | |
} | |
} |