public void CompareProtoToParser(string path) { var schemaPath = Path.Combine(Directory.GetCurrentDirectory(), SchemaPath); _output.WriteLine(Path.GetDirectoryName( Path.Combine(schemaPath, path).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); bool includeComments = IncludeComments(path); var protocBinPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "protoc.bin")); int exitCode; using (var proc = new Process()) { var psi = proc.StartInfo; psi.FileName = "protoc"; psi.Arguments = $"--descriptor_set_out={protocBinPath} {path}"; if (includeComments) { psi.Arguments += " --include_source_info"; } psi.RedirectStandardError = psi.RedirectStandardOutput = true; psi.UseShellExecute = false; psi.WorkingDirectory = schemaPath; proc.Start(); var stdout = proc.StandardOutput.ReadToEndAsync(); var stderr = proc.StandardError.ReadToEndAsync(); if (!proc.WaitForExit(5000)) { try { proc.Kill(); } catch { } } exitCode = proc.ExitCode; string err = "", @out = ""; if (stdout.Wait(1000)) { @out = stdout.Result; } if (stderr.Wait(1000)) { err = stderr.Result; } if (!string.IsNullOrWhiteSpace(@out)) { _output.WriteLine("stdout: "); _output.WriteLine(@out); } if (!string.IsNullOrWhiteSpace(err)) { _output.WriteLine("stderr: "); _output.WriteLine(err); } } FileDescriptorSet set; string protocJson = null, jsonPath; if (exitCode == 0) { using (var file = File.OpenRead(protocBinPath)) { set = Serializer.Deserialize <FileDescriptorSet>(file); protocJson = JsonConvert.SerializeObject(set, Formatting.Indented, jsonSettings); jsonPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "protoc.json")); File.WriteAllText(jsonPath, protocJson); } } set = new FileDescriptorSet(); set.AddImportPath(schemaPath); bool isProto3 = set.Add(path, includeInOutput: true) && set.Files[0].Syntax == "proto3"; if (isProto3) { using (var proc = new Process()) { var psi = proc.StartInfo; psi.FileName = "protoc"; psi.Arguments = $"--csharp_out={Path.GetDirectoryName(protocBinPath)} {path}"; psi.RedirectStandardError = psi.RedirectStandardOutput = true; psi.UseShellExecute = false; psi.WorkingDirectory = schemaPath; proc.Start(); var stdout = proc.StandardOutput.ReadToEndAsync(); var stderr = proc.StandardError.ReadToEndAsync(); if (!proc.WaitForExit(5000)) { try { proc.Kill(); } catch { } } exitCode = proc.ExitCode; string err = "", @out = ""; if (stdout.Wait(1000)) { @out = stdout.Result; } if (stderr.Wait(1000)) { err = stderr.Result; } if (!string.IsNullOrWhiteSpace(@out)) { _output.WriteLine("stdout (C#): "); _output.WriteLine(@out); } if (!string.IsNullOrWhiteSpace(err)) { _output.WriteLine("stderr (C#): "); _output.WriteLine(err); } _output.WriteLine("exit code(C#): " + exitCode); } } set.Process(); var parserBinPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "parser.bin")); using (var file = File.Create(parserBinPath)) { set.Serialize(file, false); } var parserJson = set.Serialize((s, o) => JsonConvert.SerializeObject(s, Formatting.Indented, jsonSettings), false); var errors = set.GetErrors(); Exception genError = null; try { foreach (var file in CSharpCodeGenerator.Default.Generate(set)) { var newExtension = "parser" + Path.GetExtension(file.Name); var newFileName = Path.ChangeExtension(file.Name, newExtension); File.WriteAllText(Path.Combine(schemaPath, newFileName), file.Text); } } catch (Exception ex) { genError = ex; _output.WriteLine(ex.Message); _output.WriteLine(ex.StackTrace); } jsonPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "parser.json")); File.WriteAllText(jsonPath, parserJson); if (errors.Any()) { _output.WriteLine("Parser errors:"); foreach (var err in errors) { _output.WriteLine(err.ToString()); } } _output.WriteLine("Protoc exited with code " + exitCode); var errorCount = errors.Count(x => x.IsError); if (exitCode == 0) { Assert.Equal(0, errorCount); } else { Assert.NotEqual(0, errorCount); } var parserBytes = File.ReadAllBytes(parserBinPath); using (var ms = new MemoryStream(parserBytes)) { var selfLoad = Serializer.Deserialize <FileDescriptorSet>(ms); var selfLoadJson = JsonConvert.SerializeObject(selfLoad, Formatting.Indented, jsonSettings); // should still be the same! Assert.Equal(parserJson, selfLoadJson); } var parserHex = GetPrettyHex(parserBytes); File.WriteAllText(Path.ChangeExtension(parserBinPath, "parser.hex"), parserHex); if (exitCode == 0) { var protocHex = GetPrettyHex(File.ReadAllBytes(protocBinPath)); File.WriteAllText(Path.ChangeExtension(protocBinPath, "protoc.hex"), protocHex); switch (path) { case "google/protobuf/unittest_custom_options.proto": // this is a special case; the two encoders choose slightly different // layouts for the same data; both are valid; I'm happy that this is OK // - this was why the "decode" tool (on the website) was written! break; default: // compare results Assert.Equal(protocJson, parserJson); Assert.Equal(protocHex, parserHex); break; } } Assert.Null(genError); }
public void CompareProtoToParser(string path, bool includeImports) { if (path == "google/protobuf/map_unittest_proto3.proto") { return; // TODO known oddity } var schemaPath = Path.Combine(Directory.GetCurrentDirectory(), SchemaPath); _output.WriteLine(Path.GetDirectoryName( Path.Combine(schemaPath, path).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); bool includeComments = IncludeComments(path); var protocBinPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "protoc.bin")); int exitCode; string protocExe = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"windows\protoc" : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? @"macosx/protoc" : ""; if (string.IsNullOrWhiteSpace(protocExe)) { throw new PlatformNotSupportedException(RuntimeInformation.OSDescription); } using (var proc = new Process()) { var psi = proc.StartInfo; psi.FileName = protocExe; psi.Arguments = $"--experimental_allow_proto3_optional --descriptor_set_out={protocBinPath} {path}"; if (includeComments) { psi.Arguments += " --include_source_info"; } if (includeImports) { psi.Arguments += " --include_imports"; } psi.RedirectStandardError = psi.RedirectStandardOutput = true; psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.WorkingDirectory = schemaPath; proc.Start(); var stdout = proc.StandardOutput.ReadToEndAsync(); var stderr = proc.StandardError.ReadToEndAsync(); if (!proc.WaitForExit(5000)) { try { proc.Kill(); } catch { } } exitCode = proc.ExitCode; string err = "", @out = ""; if (stdout.Wait(1000)) { @out = stdout.Result; } if (stderr.Wait(1000)) { err = stderr.Result; } if (!string.IsNullOrWhiteSpace(@out)) { _output.WriteLine("stdout: "); _output.WriteLine(@out); } if (!string.IsNullOrWhiteSpace(err)) { _output.WriteLine("stderr: "); _output.WriteLine(err); } } FileDescriptorSet set; string protocJson = null, jsonPath; if (exitCode == 0) { using var file = File.OpenRead(protocBinPath); set = CustomProtogenSerializer.Instance.Deserialize <FileDescriptorSet>(file); protocJson = JsonConvert.SerializeObject(set, Formatting.Indented, jsonSettings); jsonPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "protoc.json")); File.WriteAllText(jsonPath, protocJson); } set = new FileDescriptorSet(); set.AddImportPath(schemaPath); bool isProto3 = set.Add(path, includeInOutput: true) && set.Files[0].Syntax == "proto3"; if (isProto3) { using var proc = new Process(); var psi = proc.StartInfo; psi.FileName = protocExe; psi.Arguments = $"--experimental_allow_proto3_optional --csharp_out={Path.GetDirectoryName(protocBinPath)} {path}"; psi.RedirectStandardError = psi.RedirectStandardOutput = true; psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.WorkingDirectory = schemaPath; proc.Start(); var stdout = proc.StandardOutput.ReadToEndAsync(); var stderr = proc.StandardError.ReadToEndAsync(); if (!proc.WaitForExit(5000)) { try { proc.Kill(); } catch { } } exitCode = proc.ExitCode; string err = "", @out = ""; if (stdout.Wait(1000)) { @out = stdout.Result; } if (stderr.Wait(1000)) { err = stderr.Result; } if (!string.IsNullOrWhiteSpace(@out)) { _output.WriteLine("stdout (C#): "); _output.WriteLine(@out); } if (!string.IsNullOrWhiteSpace(err)) { _output.WriteLine("stderr (C#): "); _output.WriteLine(err); } _output.WriteLine("exit code(C#): " + exitCode); } set.Process(); set.ApplyFileDependencyOrder(); var parserBinPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "parser.bin")); using (var file = File.Create(parserBinPath)) { set.Serialize(CustomProtogenSerializer.Instance, file, includeImports); } var parserJson = set.Serialize((s, _) => JsonConvert.SerializeObject(s, Formatting.Indented, jsonSettings), includeImports); var errors = set.GetErrors(); Exception genError = null; try { var options = new Dictionary <string, string> { { "services", "true" }, }; foreach (var file in CSharpCodeGenerator.Default.Generate(set, options: options)) { var newExtension = "parser" + Path.GetExtension(file.Name); var newFileName = Path.ChangeExtension(file.Name, newExtension); File.WriteAllText(Path.Combine(schemaPath, newFileName), file.Text); } } catch (Exception ex) { genError = ex; _output.WriteLine(ex.Message); _output.WriteLine(ex.StackTrace); } jsonPath = Path.Combine(schemaPath, Path.ChangeExtension(path, "parser.json")); File.WriteAllText(jsonPath, parserJson); if (errors.Length > 0) { _output.WriteLine("Parser errors:"); foreach (var err in errors) { _output.WriteLine(err.ToString()); } } _output.WriteLine("Protoc exited with code " + exitCode); var errorCount = errors.Count(x => x.IsError); if (exitCode == 0) { Assert.Equal(0, errorCount); } else { Assert.NotEqual(0, errorCount); } var parserBytes = File.ReadAllBytes(parserBinPath); using (var ms = new MemoryStream(parserBytes)) { var selfLoad = CustomProtogenSerializer.Instance.Deserialize <FileDescriptorSet>(ms); var selfLoadJson = JsonConvert.SerializeObject(selfLoad, Formatting.Indented, jsonSettings); // should still be the same! Assert.Equal(parserJson, selfLoadJson); } var parserHex = GetPrettyHex(parserBytes); File.WriteAllText(Path.ChangeExtension(parserBinPath, "parser.hex"), parserHex); if (exitCode == 0) { var protocHex = GetPrettyHex(File.ReadAllBytes(protocBinPath)); File.WriteAllText(Path.ChangeExtension(protocBinPath, "protoc.hex"), protocHex); switch (path) { case "google/protobuf/unittest_custom_options.proto": case "advancedOptions.proto": // these are special cases; the two encoders choose slightly different // layouts for the same data; both are valid; I'm happy that this is OK // - this was why the "decode" tool (on the website) was written! break; case "google/protobuf/unittest.proto": // ^^^ different layout of an integer; "2e+8" vs "200000000" - I'm fine with it // // the following end up importing unittest.proto, so have the same symptom case "google/protobuf/map_unittest.proto" when(includeImports): case "google/protobuf/unittest_optimize_for.proto" when(includeImports): case "google/protobuf/unittest_embed_optimize_for.proto" when(includeImports): case "google/protobuf/unittest_lite_imports_nonlite.proto" when(includeImports): case "google/protobuf/unittest_no_field_presence.proto" when(includeImports): break; default: // compare results Assert.Equal(protocJson, parserJson); Assert.Equal(protocHex, parserHex); break; } } Assert.Null(genError); }