blob: 1370aa1c668cc2f958c9aef6f98271211c398b0c [file] [log] [blame]
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.IO;
using Microsoft.Build.Framework;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class ProtoCompileCommandLineGeneratorTest : ProtoCompileBasicTest
{
[SetUp]
public new void SetUp()
{
_task.Generator = "csharp";
_task.OutputDir = "outdir";
_task.Protobuf = Utils.MakeSimpleItems("a.proto");
}
void ExecuteExpectSuccess()
{
_mockEngine
.Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback((BuildErrorEventArgs e) =>
Assert.Fail($"Error logged by build engine:\n{e.Message}"));
bool result = _task.Execute();
Assert.IsTrue(result);
}
[Test]
public void MinimalCompile()
{
ExecuteExpectSuccess();
Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$"));
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "--error_format=msvs", "a.proto" }));
}
[Test]
public void CompileTwoFiles()
{
_task.Protobuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto");
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "--error_format=msvs", "a.proto", "foo/b.proto" }));
}
[Test]
public void CompileWithProtoPaths()
{
_task.ProtoPath = new[] { "/path1", "/path2" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "--proto_path=/path1",
"--proto_path=/path2", "--error_format=msvs", "a.proto" }));
}
[TestCase("Cpp")]
[TestCase("CSharp")]
[TestCase("Java")]
[TestCase("Javanano")]
[TestCase("Js")]
[TestCase("Objc")]
[TestCase("Php")]
[TestCase("Python")]
[TestCase("Ruby")]
public void CompileWithOptions(string gen)
{
_task.Generator = gen;
_task.OutputOptions = new[] { "foo", "bar" };
ExecuteExpectSuccess();
gen = gen.ToLowerInvariant();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
$"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "--error_format=msvs", "a.proto" }));
}
[Test]
public void OutputDependencyFile()
{
_task.DependencyOut = "foo/my.protodep";
// Task fails trying to read the non-generated file; we ignore that.
_task.Execute();
Assert.That(_task.LastResponseFile,
Does.Contain("--dependency_out=foo/my.protodep"));
}
[Test]
public void OutputDependencyWithProtoDepDir()
{
_task.ProtoDepDir = "foo";
// Task fails trying to read the non-generated file; we ignore that.
_task.Execute();
Assert.That(_task.LastResponseFile,
Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$"));
}
[Test]
public void GenerateGrpc()
{
_task.GrpcPluginExe = "/foo/grpcgen";
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--csharp_out=outdir", "--grpc_out=outdir",
"--plugin=protoc-gen-grpc=/foo/grpcgen" }));
}
[Test]
public void GenerateGrpcWithOutDir()
{
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputDir = "gen-out";
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--csharp_out=outdir", "--grpc_out=gen-out" }));
}
[Test]
public void GenerateGrpcWithOptions()
{
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputOptions = new[] { "baz", "quux" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--grpc_opt=baz,quux"));
}
[Test]
public void AdditionalProtocArguments()
{
_task.AdditionalProtocArguments = new[] { "--experimental_allow_proto3_optional" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--experimental_allow_proto3_optional"));
}
[Test]
public void DirectoryArgumentsSlashTrimmed()
{
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputDir = "gen-out/";
_task.OutputDir = "outdir/";
_task.ProtoPath = new[] { "/path1/", "/path2/" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--proto_path=/path1", "--proto_path=/path2",
"--csharp_out=outdir", "--grpc_out=gen-out" }));
}
[TestCase(".", ".")]
[TestCase("/", "/")]
[TestCase("//", "/")]
[TestCase("/foo/", "/foo")]
[TestCase("/foo", "/foo")]
[TestCase("foo/", "foo")]
[TestCase("foo//", "foo")]
[TestCase("foo/\\", "foo")]
[TestCase("foo\\/", "foo")]
[TestCase("C:\\foo", "C:\\foo")]
[TestCase("C:", "C:")]
[TestCase("C:\\", "C:\\")]
[TestCase("C:\\\\", "C:\\")]
public void DirectorySlashTrimmingCases(string given, string expect)
{
if (Path.DirectorySeparatorChar == '/')
expect = expect.Replace('\\', '/');
_task.OutputDir = given;
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--csharp_out=" + expect));
}
[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.",
"../Protos/greet.proto",
19,
5,
"warning : When enum name is stripped and label is PascalCased (Zero) this value label conflicts with Zero.")]
[TestCase(
"../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used.",
"../Protos/greet.proto",
0,
0,
"Import google/protobuf/empty.proto but not used.")]
[TestCase("../Protos/greet.proto(14) : error in column=10: \"name\" is already defined in \"Greet.HelloRequest\".", null, 0, 0, null)]
[TestCase("../Protos/greet.proto: Import \"google / protobuf / empty.proto\" was listed twice.", null, 0, 0, null)]
[TestCase("[libprotobuf WARNING T:\\altsrc\\github\\...\\csharp\\csharp_enum.cc:74] Duplicate enum value Work (originally Work) in PhoneType; adding underscore to distinguish",
"T:\\altsrc\\github\\...\\csharp\\csharp_enum.cc",
74, 0,
"Duplicate enum value Work (originally Work) in PhoneType; adding underscore to distinguish")]
[TestCase("[libprotobuf ERROR T:\\path\\...\\filename:23] Some message", null, 0, 0, null)]
[TestCase("[libprotobuf FATAL T:\\path\\...\\filename:23] Some message", null, 0, 0, null)]
public void WarningsParsed(string stderr, string file, int line, int col, string message)
{
_task.StdErrMessages.Add(stderr);
bool matched = false;
_mockEngine
.Setup(me => me.LogWarningEvent(It.IsAny<BuildWarningEventArgs>()))
.Callback((BuildWarningEventArgs e) => {
matched = true;
if (file != null)
{
Assert.AreEqual(file, e.File);
Assert.AreEqual(line, e.LineNumber);
Assert.AreEqual(col, e.ColumnNumber);
Assert.AreEqual(message, e.Message);
}
else
{
Assert.Fail($"Error logged by build engine:\n{e.Message}");
}
});
bool result = _task.Execute();
Assert.IsFalse(result);
// To get here in the test then either the event fired and the values matched
// or the event did not fire (input did not parse as a warning).
// If it did not parse as a warning then check that the expected message is null
if (!matched && message != null)
{
Assert.Fail($"Expected match: {message}");
}
}
[TestCase(
"../Protos/greet.proto(14) : error in column=10: \"name\" is already defined in \"Greet.HelloRequest\".",
"../Protos/greet.proto",
14,
10,
"\"name\" is already defined in \"Greet.HelloRequest\".")]
[TestCase(
"../Protos/greet.proto: Import \"google / protobuf / empty.proto\" was listed twice.",
"../Protos/greet.proto",
0,
0,
"Import \"google / protobuf / empty.proto\" was listed twice.")]
[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)]
[TestCase("../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used.", null, 0, 0, null)]
[TestCase("[libprotobuf WARNING T:\\altsrc\\github\\...\\csharp\\csharp_enum.cc:74] Duplicate enum value Work (originally Work) in PhoneType; adding underscore to distinguish",
null, 0, 0, null)]
[TestCase("[libprotobuf ERROR T:\\path\\...\\filename:23] Some message",
"T:\\path\\...\\filename", 23, 0, "ERROR Some message")]
[TestCase("[libprotobuf FATAL T:\\path\\...\\filename:23] Some message",
"T:\\path\\...\\filename", 23, 0, "FATAL Some message")]
public void ErrorsParsed(string stderr, string file, int line, int col, string message)
{
_task.StdErrMessages.Add(stderr);
bool matched = false;
_mockEngine
.Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback((BuildErrorEventArgs e) => {
matched = true;
if (file != null)
{
Assert.AreEqual(file, e.File);
Assert.AreEqual(line, e.LineNumber);
Assert.AreEqual(col, e.ColumnNumber);
Assert.AreEqual(message, e.Message);
}
else
{
// Ignore expected error
// "protoc/protoc.exe" existed with code -1.
if (!e.Message.EndsWith("exited with code -1."))
{
Assert.Fail($"Error logged by build engine:\n{e.Message}");
}
}
});
bool result = _task.Execute();
Assert.IsFalse(result);
// To get here in the test then either the event fired and the values matched
// or the event did not fire (input did not parse as an error).
// If it did not parse as an error then check that the expected message is null
if (!matched && message != null)
{
Assert.Fail($"Expected match: {message}");
}
}
};
}