1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using NUnit.Framework; 11 using System.Diagnostics; 12 using System; 13 using System.Reflection; 14 using System.IO; 15 16 namespace Google.Protobuf 17 { 18 public class RefStructCompatibilityTest 19 { 20 /// <summary> 21 /// Checks that the generated code can be compiler with an old C# compiler. 22 /// The reason why this test is needed is that even though dotnet SDK projects allow to set LangVersion, 23 /// this setting doesn't accurately simulate compilation with an actual old pre-roslyn C# compiler. 24 /// For instance, the "ref struct" types are only supported by C# 7.2 and higher, but even if 25 /// LangVersion is set low, the roslyn compiler still understands the concept of ref struct 26 /// and silently accepts them. Therefore we try to build the generated code with an actual old C# compiler 27 /// to be able to catch these sort of compatibility problems. 28 /// </summary> 29 [Test] GeneratedCodeCompilesWithOldCsharpCompiler()30 public void GeneratedCodeCompilesWithOldCsharpCompiler() 31 { 32 if (Environment.OSVersion.Platform != PlatformID.Win32NT) 33 { 34 // This tests needs old C# compiler which is only available on Windows. Skipping it on all other platforms. 35 return; 36 } 37 38 var currentAssemblyDir = Path.GetDirectoryName(typeof(RefStructCompatibilityTest).GetTypeInfo().Assembly.Location); 39 var testProtosProjectDir = Path.GetFullPath(Path.Combine(currentAssemblyDir, "..", "..", "..", "..", "Google.Protobuf.Test.TestProtos")); 40 var testProtosOutputDir = (currentAssemblyDir.Contains("bin/Debug/") || currentAssemblyDir.Contains("bin\\Debug\\")) ? "bin\\Debug\\net462" : "bin\\Release\\net462"; 41 42 // If "ref struct" types are used in the generated code, compilation with an old compiler will fail with the following error: 43 // "XYZ is obsolete: 'Types with embedded references are not supported in this version of your compiler.'" 44 // We build the code with GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE to avoid the use of ref struct in the generated code. 45 var compatibilityFlag = "-define:GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE"; 46 var sources = "*.cs"; // the generated sources from the TestProtos project 47 // We suppress CS1691, which flags a warning for the generated line of 48 // #pragma warning disable 1591, 0612, 3021, 8981 49 // because CS8981 is unknown to this version of the compiler. 50 var args = $"-langversion:3 -nologo -nowarn:1691 -target:library {compatibilityFlag} -reference:{testProtosOutputDir}\\Google.Protobuf.dll -out:{testProtosOutputDir}\\TestProtos.RefStructCompatibilityTest.OldCompiler.dll {sources}"; 51 RunOldCsharpCompilerAndCheckSuccess(args, testProtosProjectDir); 52 } 53 54 /// <summary> 55 /// Invoke an old C# compiler in a subprocess and check it finished successful. 56 /// </summary> 57 /// <param name="args"></param> 58 /// <param name="workingDirectory"></param> RunOldCsharpCompilerAndCheckSuccess(string args, string workingDirectory)59 private void RunOldCsharpCompilerAndCheckSuccess(string args, string workingDirectory) 60 { 61 using var process = new Process(); 62 63 // Get the path to the old C# 5 compiler from .NET framework. This approach is not 100% reliable, but works on most machines. 64 // Alternative way of getting the framework path is System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory() 65 // but it only works with the net45 target. 66 var oldCsharpCompilerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v4.0.30319", "csc.exe"); 67 process.StartInfo.FileName = oldCsharpCompilerPath; 68 process.StartInfo.RedirectStandardOutput = true; 69 process.StartInfo.RedirectStandardError = true; 70 process.StartInfo.UseShellExecute = false; 71 process.StartInfo.Arguments = args; 72 process.StartInfo.WorkingDirectory = workingDirectory; 73 74 process.OutputDataReceived += (sender, e) => 75 { 76 if (e.Data != null) 77 { 78 Console.WriteLine(e.Data); 79 } 80 }; 81 process.ErrorDataReceived += (sender, e) => 82 { 83 if (e.Data != null) 84 { 85 Console.WriteLine(e.Data); 86 } 87 }; 88 89 process.Start(); 90 91 process.BeginErrorReadLine(); 92 process.BeginOutputReadLine(); 93 94 process.WaitForExit(); 95 Assert.AreEqual(0, process.ExitCode); 96 } 97 } 98 }