1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using NUnit.Framework; 34 using System.Diagnostics; 35 using System; 36 using System.Reflection; 37 using System.IO; 38 39 namespace Google.Protobuf 40 { 41 public class RefStructCompatibilityTest 42 { 43 /// <summary> 44 /// Checks that the generated code can be compiler with an old C# compiler. 45 /// The reason why this test is needed is that even though dotnet SDK projects allow to set LangVersion, 46 /// this setting doesn't accurately simulate compilation with an actual old pre-roslyn C# compiler. 47 /// For instance, the "ref struct" types are only supported by C# 7.2 and higher, but even if 48 /// LangVersion is set low, the roslyn compiler still understands the concept of ref struct 49 /// and silently accepts them. Therefore we try to build the generated code with an actual old C# compiler 50 /// to be able to catch these sort of compatibility problems. 51 /// </summary> 52 [Test] GeneratedCodeCompilesWithOldCsharpCompiler()53 public void GeneratedCodeCompilesWithOldCsharpCompiler() 54 { 55 if (Environment.OSVersion.Platform != PlatformID.Win32NT) 56 { 57 // This tests needs old C# compiler which is only available on Windows. Skipping it on all other platforms. 58 return; 59 } 60 61 var currentAssemblyDir = Path.GetDirectoryName(typeof(RefStructCompatibilityTest).GetTypeInfo().Assembly.Location); 62 var testProtosProjectDir = Path.GetFullPath(Path.Combine(currentAssemblyDir, "..", "..", "..", "..", "Google.Protobuf.Test.TestProtos")); 63 var testProtosOutputDir = (currentAssemblyDir.Contains("bin/Debug/") || currentAssemblyDir.Contains("bin\\Debug\\")) ? "bin\\Debug\\net45" : "bin\\Release\\net45"; 64 65 // If "ref struct" types are used in the generated code, compilation with an old compiler will fail with the following error: 66 // "XYZ is obsolete: 'Types with embedded references are not supported in this version of your compiler.'" 67 // We build the code with GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE to avoid the use of ref struct in the generated code. 68 var compatibilityFlag = "-define:GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE"; 69 var sources = "*.cs"; // the generated sources from the TestProtos project 70 var args = $"-langversion:3 -target:library {compatibilityFlag} -reference:{testProtosOutputDir}\\Google.Protobuf.dll -out:{testProtosOutputDir}\\TestProtos.RefStructCompatibilityTest.OldCompiler.dll {sources}"; 71 RunOldCsharpCompilerAndCheckSuccess(args, testProtosProjectDir); 72 } 73 74 /// <summary> 75 /// Invoke an old C# compiler in a subprocess and check it finished successful. 76 /// </summary> 77 /// <param name="args"></param> 78 /// <param name="workingDirectory"></param> RunOldCsharpCompilerAndCheckSuccess(string args, string workingDirectory)79 private void RunOldCsharpCompilerAndCheckSuccess(string args, string workingDirectory) 80 { 81 using (var process = new Process()) 82 { 83 // Get the path to the old C# 5 compiler from .NET framework. This approach is not 100% reliable, but works on most machines. 84 // Alternative way of getting the framework path is System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory() 85 // but it only works with the net45 target. 86 var oldCsharpCompilerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v4.0.30319", "csc.exe"); 87 process.StartInfo.FileName = oldCsharpCompilerPath; 88 process.StartInfo.RedirectStandardOutput = true; 89 process.StartInfo.RedirectStandardError = true; 90 process.StartInfo.UseShellExecute = false; 91 process.StartInfo.Arguments = args; 92 process.StartInfo.WorkingDirectory = workingDirectory; 93 94 process.OutputDataReceived += (sender, e) => 95 { 96 if (e.Data != null) 97 { 98 Console.WriteLine(e.Data); 99 } 100 }; 101 process.ErrorDataReceived += (sender, e) => 102 { 103 if (e.Data != null) 104 { 105 Console.WriteLine(e.Data); 106 } 107 }; 108 109 process.Start(); 110 111 process.BeginErrorReadLine(); 112 process.BeginOutputReadLine(); 113 114 process.WaitForExit(); 115 Assert.AreEqual(0, process.ExitCode); 116 } 117 } 118 } 119 }