1 #region Copyright notice and license 2 3 // Copyright 2018 gRPC authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 #endregion 18 19 using System.IO; 20 using Microsoft.Build.Framework; 21 using Moq; 22 using NUnit.Framework; 23 24 namespace Grpc.Tools.Tests 25 { 26 public class ProtoCompileCommandLineGeneratorTest : ProtoCompileBasicTest 27 { 28 [SetUp] SetUp()29 public new void SetUp() 30 { 31 _task.Generator = "csharp"; 32 _task.OutputDir = "outdir"; 33 _task.Protobuf = Utils.MakeSimpleItems("a.proto"); 34 } 35 ExecuteExpectSuccess()36 void ExecuteExpectSuccess() 37 { 38 _mockEngine 39 .Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>())) 40 .Callback((BuildErrorEventArgs e) => 41 Assert.Fail($"Error logged by build engine:\n{e.Message}")); 42 bool result = _task.Execute(); 43 Assert.IsTrue(result); 44 } 45 46 [Test] MinimalCompile()47 public void MinimalCompile() 48 { 49 ExecuteExpectSuccess(); 50 Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); 51 Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { 52 "--csharp_out=outdir", "--error_format=msvs", "a.proto" })); 53 } 54 55 [Test] CompileTwoFiles()56 public void CompileTwoFiles() 57 { 58 _task.Protobuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); 59 ExecuteExpectSuccess(); 60 Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { 61 "--csharp_out=outdir", "--error_format=msvs", "a.proto", "foo/b.proto" })); 62 } 63 64 [Test] CompileWithProtoPaths()65 public void CompileWithProtoPaths() 66 { 67 _task.ProtoPath = new[] { "/path1", "/path2" }; 68 ExecuteExpectSuccess(); 69 Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { 70 "--csharp_out=outdir", "--proto_path=/path1", 71 "--proto_path=/path2", "--error_format=msvs", "a.proto" })); 72 } 73 74 [TestCase("Cpp")] 75 [TestCase("CSharp")] 76 [TestCase("Java")] 77 [TestCase("Javanano")] 78 [TestCase("Js")] 79 [TestCase("Objc")] 80 [TestCase("Php")] 81 [TestCase("Python")] 82 [TestCase("Ruby")] CompileWithOptions(string gen)83 public void CompileWithOptions(string gen) 84 { 85 _task.Generator = gen; 86 _task.OutputOptions = new[] { "foo", "bar" }; 87 ExecuteExpectSuccess(); 88 gen = gen.ToLowerInvariant(); 89 Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { 90 $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "--error_format=msvs", "a.proto" })); 91 } 92 93 [Test] OutputDependencyFile()94 public void OutputDependencyFile() 95 { 96 _task.DependencyOut = "foo/my.protodep"; 97 // Task fails trying to read the non-generated file; we ignore that. 98 _task.Execute(); 99 Assert.That(_task.LastResponseFile, 100 Does.Contain("--dependency_out=foo/my.protodep")); 101 } 102 103 [Test] OutputDependencyWithProtoDepDir()104 public void OutputDependencyWithProtoDepDir() 105 { 106 _task.ProtoDepDir = "foo"; 107 // Task fails trying to read the non-generated file; we ignore that. 108 _task.Execute(); 109 Assert.That(_task.LastResponseFile, 110 Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); 111 } 112 113 [Test] GenerateGrpc()114 public void GenerateGrpc() 115 { 116 _task.GrpcPluginExe = "/foo/grpcgen"; 117 ExecuteExpectSuccess(); 118 Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { 119 "--csharp_out=outdir", "--grpc_out=outdir", 120 "--plugin=protoc-gen-grpc=/foo/grpcgen" })); 121 } 122 123 [Test] GenerateGrpcWithOutDir()124 public void GenerateGrpcWithOutDir() 125 { 126 _task.GrpcPluginExe = "/foo/grpcgen"; 127 _task.GrpcOutputDir = "gen-out"; 128 ExecuteExpectSuccess(); 129 Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { 130 "--csharp_out=outdir", "--grpc_out=gen-out" })); 131 } 132 133 [Test] GenerateGrpcWithOptions()134 public void GenerateGrpcWithOptions() 135 { 136 _task.GrpcPluginExe = "/foo/grpcgen"; 137 _task.GrpcOutputOptions = new[] { "baz", "quux" }; 138 ExecuteExpectSuccess(); 139 Assert.That(_task.LastResponseFile, 140 Does.Contain("--grpc_opt=baz,quux")); 141 } 142 143 [Test] DirectoryArgumentsSlashTrimmed()144 public void DirectoryArgumentsSlashTrimmed() 145 { 146 _task.GrpcPluginExe = "/foo/grpcgen"; 147 _task.GrpcOutputDir = "gen-out/"; 148 _task.OutputDir = "outdir/"; 149 _task.ProtoPath = new[] { "/path1/", "/path2/" }; 150 ExecuteExpectSuccess(); 151 Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { 152 "--proto_path=/path1", "--proto_path=/path2", 153 "--csharp_out=outdir", "--grpc_out=gen-out" })); 154 } 155 156 [TestCase(".", ".")] 157 [TestCase("/", "/")] 158 [TestCase("//", "/")] 159 [TestCase("/foo/", "/foo")] 160 [TestCase("/foo", "/foo")] 161 [TestCase("foo/", "foo")] 162 [TestCase("foo//", "foo")] 163 [TestCase("foo/\\", "foo")] 164 [TestCase("foo\\/", "foo")] 165 [TestCase("C:\\foo", "C:\\foo")] 166 [TestCase("C:", "C:")] 167 [TestCase("C:\\", "C:\\")] 168 [TestCase("C:\\\\", "C:\\")] DirectorySlashTrimmingCases(string given, string expect)169 public void DirectorySlashTrimmingCases(string given, string expect) 170 { 171 if (Path.DirectorySeparatorChar == '/') 172 expect = expect.Replace('\\', '/'); 173 _task.OutputDir = given; 174 ExecuteExpectSuccess(); 175 Assert.That(_task.LastResponseFile, 176 Does.Contain("--csharp_out=" + expect)); 177 } 178 179 [TestCase( 180 "../Protos/greet.proto(19) : warning in column=5 : warning : When enum name is stripped and label is PascalCased (Zero) this value label conflicts with Zero.", 181 "../Protos/greet.proto", 182 19, 183 5, 184 "warning : When enum name is stripped and label is PascalCased (Zero) this value label conflicts with Zero.")] 185 [TestCase( 186 "../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used.", 187 "../Protos/greet.proto", 188 0, 189 0, 190 "Import google/protobuf/empty.proto but not used.")] 191 [TestCase("../Protos/greet.proto(14) : error in column=10: \"name\" is already defined in \"Greet.HelloRequest\".", null, 0, 0, null)] 192 [TestCase("../Protos/greet.proto: Import \"google / protobuf / empty.proto\" was listed twice.", null, 0, 0, null)] WarningsParsed(string stderr, string file, int line, int col, string message)193 public void WarningsParsed(string stderr, string file, int line, int col, string message) 194 { 195 _task.StdErrMessages.Add(stderr); 196 197 _mockEngine 198 .Setup(me => me.LogWarningEvent(It.IsAny<BuildWarningEventArgs>())) 199 .Callback((BuildWarningEventArgs e) => { 200 if (file != null) 201 { 202 Assert.AreEqual(file, e.File); 203 Assert.AreEqual(line, e.LineNumber); 204 Assert.AreEqual(col, e.ColumnNumber); 205 Assert.AreEqual(message, e.Message); 206 } 207 else 208 { 209 Assert.Fail($"Error logged by build engine:\n{e.Message}"); 210 } 211 }); 212 213 bool result = _task.Execute(); 214 Assert.IsFalse(result); 215 } 216 217 [TestCase( 218 "../Protos/greet.proto(14) : error in column=10: \"name\" is already defined in \"Greet.HelloRequest\".", 219 "../Protos/greet.proto", 220 14, 221 10, 222 "\"name\" is already defined in \"Greet.HelloRequest\".")] 223 [TestCase( 224 "../Protos/greet.proto: Import \"google / protobuf / empty.proto\" was listed twice.", 225 "../Protos/greet.proto", 226 0, 227 0, 228 "Import \"google / protobuf / empty.proto\" was listed twice.")] 229 [TestCase("../Protos/greet.proto(19) : warning in column=5 : warning : When enum name is stripped and label is PascalCased (Zero) this value label conflicts with Zero.", null, 0, 0, null)] 230 [TestCase("../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used.", null, 0, 0, null)] ErrorsParsed(string stderr, string file, int line, int col, string message)231 public void ErrorsParsed(string stderr, string file, int line, int col, string message) 232 { 233 _task.StdErrMessages.Add(stderr); 234 235 _mockEngine 236 .Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>())) 237 .Callback((BuildErrorEventArgs e) => { 238 if (file != null) 239 { 240 Assert.AreEqual(file, e.File); 241 Assert.AreEqual(line, e.LineNumber); 242 Assert.AreEqual(col, e.ColumnNumber); 243 Assert.AreEqual(message, e.Message); 244 } 245 else 246 { 247 // Ignore expected error 248 // "protoc/protoc.exe" existed with code -1. 249 if (!e.Message.EndsWith("exited with code -1.")) 250 { 251 Assert.Fail($"Error logged by build engine:\n{e.Message}"); 252 } 253 } 254 }); 255 256 bool result = _task.Execute(); 257 Assert.IsFalse(result); 258 } 259 }; 260 } 261