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 33using NUnit.Framework; 34using System.Diagnostics; 35using System; 36using System.Reflection; 37using System.IO; 38 39namespace 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] 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> 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}