#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 NUnit.Framework; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; namespace Google.ProtocolBuffers.ProtoGen { /// /// Tests protoc-gen-cs plugin. /// [TestFixture] [Category("Preprocessor")] public partial class ProtocGenCsUnittests { private static readonly string TempPath = Path.Combine(Path.GetTempPath(), "protoc-gen-cs.Test"); private const string DefaultProto = @" package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; }"; #region TestFixture SetUp/TearDown private static readonly string OriginalWorkingDirectory = Environment.CurrentDirectory; private StringBuilder buffer = new StringBuilder(); [TestFixtureSetUp] public virtual void Setup() { Teardown(); Directory.CreateDirectory(TempPath); Environment.CurrentDirectory = TempPath; this.buffer.Length = 0; } [TestFixtureTearDown] public virtual void Teardown() { Environment.CurrentDirectory = OriginalWorkingDirectory; if (Directory.Exists(TempPath)) { Directory.Delete(TempPath, true); } } #endregion #region Helper Methods RunProtoGen / RunCsc private void RunProtoc(int expect, string protoFile, params string[] args) { string protoPath = string.Format("-I. -I\"{0}\"", OriginalWorkingDirectory); string plugin = string.Format("--plugin=\"{0}\"", Path.Combine(OriginalWorkingDirectory, "protoc-gen-cs.exe")); string csOut = args.Length == 0 ? "--cs_out=." : string.Format("--cs_out=\"{0}:.\"", string.Join(" ", args)); // Start the child process. Process p = new Process(); // Redirect the output stream of the child process. p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardError = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.WorkingDirectory = TempPath; p.StartInfo.FileName = Path.Combine(OriginalWorkingDirectory, "protoc.exe"); p.StartInfo.Arguments = string.Join(" ", new string[] { plugin, csOut, protoPath, protoFile }); p.Start(); // Read the output stream first and then wait. buffer.AppendLine(string.Format("{0}> \"{1}\" {2}", p.StartInfo.WorkingDirectory, p.StartInfo.FileName, p.StartInfo.Arguments)); buffer.AppendLine(p.StandardError.ReadToEnd()); buffer.AppendLine(p.StandardOutput.ReadToEnd()); p.WaitForExit(); Assert.AreEqual(expect, p.ExitCode, this.buffer.ToString()); } private Assembly RunCsc(int expect, params string[] sources) { using (TempFile tempDll = new TempFile(String.Empty)) { tempDll.ChangeExtension(".dll"); List args = new List(); args.Add("/nologo"); args.Add("/target:library"); args.Add("/debug-"); args.Add(String.Format(@"""/out:{0}""", tempDll.TempPath)); args.Add("/r:System.dll"); args.Add(String.Format(@"""/r:{0}""", typeof(Google.ProtocolBuffers.DescriptorProtos.DescriptorProto).Assembly. Location)); args.AddRange(sources); string exe = Path.Combine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), "csc.exe"); ProcessStartInfo psi = new ProcessStartInfo(exe); psi.WorkingDirectory = TempPath; psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.RedirectStandardError = true; psi.Arguments = string.Join(" ", args.ToArray()); Process p = Process.Start(psi); buffer.AppendLine(string.Format("{0}> \"{1}\" {2}", p.StartInfo.WorkingDirectory, p.StartInfo.FileName, p.StartInfo.Arguments)); buffer.AppendLine(p.StandardError.ReadToEnd()); buffer.AppendLine(p.StandardOutput.ReadToEnd()); p.WaitForExit(); Assert.AreEqual(expect, p.ExitCode, this.buffer.ToString()); Assembly asm = null; if (p.ExitCode == 0) { byte[] allbytes = File.ReadAllBytes(tempDll.TempPath); asm = Assembly.Load(allbytes); foreach (Type t in asm.GetTypes()) { Debug.WriteLine(t.FullName, asm.FullName); } } return asm; } } #endregion // ******************************************************************* // The following tests excercise options for protogen.exe // ******************************************************************* [Test] public void TestProtoFile() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileWithConflictingType() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using ( ProtoFile proto = new ProtoFile(test + ".proto", @" package nunit.simple; // Test a very simple message. message " + test + @" { optional string name = 1; } ")) { RunProtoc(0, proto.TempPath); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple." + test, true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple.Proto." + test, true, true); } } [Test] public void TestProtoFileWithNamespace() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-namespace=MyNewNamespace"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("MyNewNamespace.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("MyNewNamespace." + test, true, true); } } [Test] public void TestProtoFileWithUmbrellaClassName() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach("MyUmbrellaClassname.cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "/umbrella_classname=MyUmbrellaClassname"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple.MyUmbrellaClassname", true, true); } } [Test] public void TestProtoFileWithNestedClass() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-nest_classes=true"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple." + test + "+MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileWithExpandedNsDirectories() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(@"nunit\simple\" + test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-expand_namespace_directories=true"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileWithNewExtension() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".Generated.cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-file_extension=.Generated.cs"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileWithUmbrellaNamespace() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-umbrella_namespace=MyUmbrella.Namespace"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple.MyUmbrella.Namespace." + test, true, true); } } [Test] public void TestProtoFileWithIgnoredUmbrellaNamespaceDueToNesting() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(0, proto.TempPath, "-nest_classes=true", "-umbrella_namespace=MyUmbrella.Namespace"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple." + test + "+MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileWithExplicitEmptyUmbrellaNamespace() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using ( ProtoFile proto = new ProtoFile(test + ".proto", @" package nunit.simple; // Test a very simple message. message " + test + @" { optional string name = 1; } ")) { //Forces the umbrella class to not use a namespace even if a collision with a type is detected. RunProtoc(0, proto.TempPath, "-umbrella_namespace="); //error CS0441: 'nunit.simple.TestProtoFileWithExplicitEmptyUmbrellaNamespace': a class cannot be both static and sealed RunCsc(1, source.TempPath); } } [Test] public void TestProtoFileWithNewOutputFolder() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(@"generated-code\" + test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { RunProtoc(1, proto.TempPath, "-output_directory=generated-code"); Directory.CreateDirectory("generated-code"); RunProtoc(0, proto.TempPath, "-output_directory=generated-code"); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } [Test] public void TestProtoFileAndIgnoreGoogleProtobuf() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using ( ProtoFile proto = new ProtoFile(test + ".proto", @" import ""google/protobuf/csharp_options.proto""; option (google.protobuf.csharp_file_options).namespace = ""MyNewNamespace""; " + DefaultProto)) { string google = Path.Combine(TempPath, "google\\protobuf"); Directory.CreateDirectory(google); foreach (string file in Directory.GetFiles(Path.Combine(OriginalWorkingDirectory, "google\\protobuf"))) { File.Copy(file, Path.Combine(google, Path.GetFileName(file))); } Assert.AreEqual(0, Directory.GetFiles(TempPath, "*.cs").Length); RunProtoc(0, proto.TempPath); Assert.AreEqual(1, Directory.GetFiles(TempPath, "*.cs").Length); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("MyNewNamespace.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("MyNewNamespace." + test, true, true); } } [Test] public void TestProtoFileWithoutIgnoreGoogleProtobuf() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using ( ProtoFile proto = new ProtoFile(test + ".proto", @" import ""google/protobuf/csharp_options.proto""; option (google.protobuf.csharp_file_options).namespace = ""MyNewNamespace""; " + DefaultProto)) { string google = Path.Combine(TempPath, "google\\protobuf"); Directory.CreateDirectory(google); foreach (string file in Directory.GetFiles(Path.Combine(OriginalWorkingDirectory, "google\\protobuf"))) { File.Copy(file, Path.Combine(google, Path.GetFileName(file))); } Assert.AreEqual(0, Directory.GetFiles(TempPath, "*.cs").Length); //Without the option this fails due to being unable to resolve google/protobuf descriptors RunProtoc(0, proto.TempPath); } } // ******************************************************************* // The following tests excercise options for protoc.exe // ******************************************************************* [Test] public void TestProtoFileWithIncludeImports() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using ( ProtoFile proto = new ProtoFile(test + ".proto", @" import ""google/protobuf/csharp_options.proto""; option (google.protobuf.csharp_file_options).namespace = ""MyNewNamespace""; package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; } ") ) { string google = Path.Combine(TempPath, "google\\protobuf"); Directory.CreateDirectory(google); foreach (string file in Directory.GetFiles(Path.Combine(OriginalWorkingDirectory, "google\\protobuf"))) { File.Copy(file, Path.Combine(google, Path.GetFileName(file))); } Assert.AreEqual(0, Directory.GetFiles(TempPath, "*.cs").Length); //if you specify the protoc option --include_imports this should build three source files RunProtoc(0, proto.TempPath); Assert.AreEqual(1, Directory.GetFiles(TempPath, "*.cs").Length); //you can (and should) simply omit the inclusion of the extra source files in your project Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("MyNewNamespace.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("MyNewNamespace." + test, true, true); } } //Seems the --proto_path or -I option is non-functional for me. Maybe others have luck? [Test] public void TestProtoFileInDifferentDirectory() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", DefaultProto)) { Environment.CurrentDirectory = OriginalWorkingDirectory; RunProtoc(0, proto.TempPath); Assembly a = RunCsc(0, source.TempPath); //assert that the message type is in the expected namespace Type t = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple." + test, true, true); } } // ******************************************************************* // Handling of mutliple input files // ******************************************************************* [Test] public void TestMultipleProtoFiles() { Setup(); using (TempFile source1 = TempFile.Attach("MyMessage.cs")) using ( ProtoFile proto1 = new ProtoFile("MyMessage.proto", @" package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; }") ) using (TempFile source2 = TempFile.Attach("MyMessageList.cs")) using ( ProtoFile proto2 = new ProtoFile("MyMessageList.proto", @" package nunit.simple; import ""MyMessage.proto""; // Test a very simple message. message MyMessageList { repeated MyMessage messages = 1; }") ) { RunProtoc(0, proto1.TempPath); RunProtoc(0, proto2.TempPath); Assembly a = RunCsc(0, source1.TempPath, source2.TempPath); //assert that the message type is in the expected namespace Type t1 = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t1), "Expect an IMessage"); //assert that the message type is in the expected namespace Type t2 = a.GetType("nunit.simple.MyMessageList", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t2), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple.Proto.MyMessage", true, true); a.GetType("nunit.simple.Proto.MyMessageList", true, true); } } [Test] public void TestOneProtoFileWithBufferFile() { Setup(); using (TempFile source1 = TempFile.Attach("MyMessage.cs")) using ( ProtoFile proto1 = new ProtoFile("MyMessage.proto", @" package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; }") ) using (TempFile source2 = TempFile.Attach("MyMessageList.cs")) using ( ProtoFile proto2 = new ProtoFile("MyMessageList.proto", @" package nunit.simple; import ""MyMessage.proto""; // Test a very simple message. message MyMessageList { repeated MyMessage messages = 1; }") ) { //build the proto buffer for MyMessage RunProtoc(0, proto1.TempPath); //build the MyMessageList proto-buffer and generate code by including MyMessage.pb RunProtoc(0, proto2.TempPath); Assembly a = RunCsc(0, source1.TempPath, source2.TempPath); //assert that the message type is in the expected namespace Type t1 = a.GetType("nunit.simple.MyMessage", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t1), "Expect an IMessage"); //assert that the message type is in the expected namespace Type t2 = a.GetType("nunit.simple.MyMessageList", true, true); Assert.IsTrue(typeof(IMessage).IsAssignableFrom(t2), "Expect an IMessage"); //assert that we can find the static descriptor type a.GetType("nunit.simple.Proto.MyMessage", true, true); a.GetType("nunit.simple.Proto.MyMessageList", true, true); } } [Test] public void TestProtoFileWithService() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", @" import ""google/protobuf/csharp_options.proto""; option (google.protobuf.csharp_file_options).service_generator_type = GENERIC; package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; } // test a very simple service. service TestService { rpc Execute (MyMessage) returns (MyMessage); }")) { CopyInGoogleProtoFiles(); RunProtoc(0, proto.TempPath, "-nest_classes=false"); Assert.AreEqual(1, Directory.GetFiles(TempPath, "*.cs").Length); Assembly a = RunCsc(0, source.TempPath); //assert that the service type is in the expected namespace Type t1 = a.GetType("nunit.simple.TestService", true, true); Assert.IsTrue(typeof(IService).IsAssignableFrom(t1), "Expect an IService"); Assert.IsTrue(t1.IsAbstract, "Expect abstract class"); //assert that the Stub subclass type is in the expected namespace Type t2 = a.GetType("nunit.simple.TestService+Stub", true, true); Assert.IsTrue(t1.IsAssignableFrom(t2), "Expect a sub of TestService"); Assert.IsFalse(t2.IsAbstract, "Expect concrete class"); } } [Test] public void TestProtoFileWithServiceInternal() { string test = new StackFrame(false).GetMethod().Name; Setup(); using (TempFile source = TempFile.Attach(test + ".cs")) using (ProtoFile proto = new ProtoFile(test + ".proto", @" import ""google/protobuf/csharp_options.proto""; option (google.protobuf.csharp_file_options).service_generator_type = GENERIC; package nunit.simple; // Test a very simple message. message MyMessage { optional string name = 1; } // test a very simple service. service TestService { rpc Execute (MyMessage) returns (MyMessage); }")) { CopyInGoogleProtoFiles(); RunProtoc(0, proto.TempPath, "-nest_classes=false", "-public_classes=false"); Assert.AreEqual(1, Directory.GetFiles(TempPath, "*.cs").Length); Assembly a = RunCsc(0, source.TempPath); //assert that the service type is in the expected namespace Type t1 = a.GetType("nunit.simple.TestService", true, true); Assert.IsTrue(typeof(IService).IsAssignableFrom(t1), "Expect an IService"); Assert.IsTrue(t1.IsAbstract, "Expect abstract class"); //assert that the Stub subclass type is in the expected namespace Type t2 = a.GetType("nunit.simple.TestService+Stub", true, true); Assert.IsTrue(t1.IsAssignableFrom(t2), "Expect a sub of TestService"); Assert.IsFalse(t2.IsAbstract, "Expect concrete class"); } } private static void CopyInGoogleProtoFiles() { string google = Path.Combine(TempPath, "google\\protobuf"); Directory.CreateDirectory(google); foreach (string file in Directory.GetFiles(Path.Combine(OriginalWorkingDirectory, "google\\protobuf"))) { File.Copy(file, Path.Combine(google, Path.GetFileName(file))); } } } }