From 831fdd2be20ce5a93911275eb18d6702617720d5 Mon Sep 17 00:00:00 2001 From: Konstantin Olkhovskiy Date: Mon, 21 Aug 2023 20:41:56 +0400 Subject: [PATCH] Expect-style tests for parser --- src/compilerlib/pb_field_type.ml | 68 +++++++++ src/compilerlib/pb_field_type.mli | 28 ++++ src/compilerlib/pb_option.ml | 15 ++ src/compilerlib/pb_option.mli | 3 + src/compilerlib/pb_parsing_parse_tree.ml | 167 +++++++++++++++++++++++ src/tests/expectation/dune | 14 ++ src/tests/expectation/tests.expected | 154 +++++++++++++++++++++ src/tests/expectation/tests.ml | 67 +++++++++ 8 files changed, 516 insertions(+) create mode 100644 src/tests/expectation/dune create mode 100644 src/tests/expectation/tests.expected create mode 100644 src/tests/expectation/tests.ml diff --git a/src/compilerlib/pb_field_type.ml b/src/compilerlib/pb_field_type.ml index 3ac5b46b..ade296ed 100644 --- a/src/compilerlib/pb_field_type.ml +++ b/src/compilerlib/pb_field_type.ml @@ -111,3 +111,71 @@ let parse = function | "string" -> `String | "bytes" -> `Bytes | s -> `User_defined (unresolved_of_string s) + +open Format + +let rec pp_type_path ppf path = + match path with + | [] -> () + | [ name ] -> fprintf ppf "\"%s\"" name + | name :: rest -> + fprintf ppf "\"%s\"." name; + pp_type_path ppf rest + +let pp_builtin_type_floating_point ppf t = + match t with + | `Double -> fprintf ppf "\"Double\"" + | `Float -> fprintf ppf "\"Float\"" + +let pp_builtin_type_unsigned_int ppf t = + match t with + | `Uint32 -> fprintf ppf "\"Uint32\"" + | `Uint64 -> fprintf ppf "\"Uint64\"" + +let pp_builtin_type_signed_int ppf t = + match t with + | `Int32 -> fprintf ppf "\"Int32\"" + | `Int64 -> fprintf ppf "\"Int64\"" + | `Sint32 -> fprintf ppf "\"Sint32\"" + | `Sint64 -> fprintf ppf "\"Sint64\"" + | `Fixed32 -> fprintf ppf "\"Fixed32\"" + | `Fixed64 -> fprintf ppf "\"Fixed64\"" + | `Sfixed32 -> fprintf ppf "\"Sfixed32\"" + | `Sfixed64 -> fprintf ppf "\"Sfixed64\"" + +let pp_builtin_type_int ppf t = + match t with + | #builtin_type_unsigned_int as unsigned_int -> + pp_builtin_type_unsigned_int ppf unsigned_int + | #builtin_type_signed_int as signed_int -> + pp_builtin_type_signed_int ppf signed_int + +let pp_map_key_type ppf t = + match t with + | #builtin_type_int as int_type -> pp_builtin_type_int ppf int_type + | `Bool -> fprintf ppf "\"Bool\"" + | `String -> fprintf ppf "\"String\"" + +let pp_builtin_type ppf t = + match t with + | #builtin_type_floating_point as float_type -> + pp_builtin_type_floating_point ppf float_type + | #builtin_type_int as int_type -> pp_builtin_type_int ppf int_type + | `Bool -> fprintf ppf "\"Bool\"" + | `String -> fprintf ppf "\"String\"" + | `Bytes -> fprintf ppf "\"Bytes\"" + +let pp_unresolved ppf unresolved = + fprintf ppf + "{ @[\"type_path\": %a;@ \"type_name\": \"%s\";@ \"from_root\": %b@] }" + pp_type_path unresolved.type_path unresolved.type_name unresolved.from_root + +let pp_resolved ppf resolved = fprintf ppf "%d" resolved + +let pp_type pp_user_defined ppf t = + match t with + | #builtin_type as built_in_type -> pp_builtin_type ppf built_in_type + | `User_defined user_defined -> pp_user_defined ppf user_defined + +let pp_unresolved_t ppf t = pp_type pp_unresolved ppf t +let pp_resolved_t ppf t = pp_type pp_resolved ppf t diff --git a/src/compilerlib/pb_field_type.mli b/src/compilerlib/pb_field_type.mli index 9d752be1..b5176bab 100644 --- a/src/compilerlib/pb_field_type.mli +++ b/src/compilerlib/pb_field_type.mli @@ -128,3 +128,31 @@ type unresolved_t = unresolved t type resolved_t = resolved t val parse : string -> unresolved_t +val pp_type_path : Format.formatter -> type_path -> unit + +val pp_builtin_type_floating_point : + Format.formatter -> builtin_type_floating_point -> unit + +val pp_builtin_type_unsigned_int : + Format.formatter -> builtin_type_unsigned_int -> unit + +val pp_builtin_type_signed_int : + Format.formatter -> builtin_type_signed_int -> unit + +val pp_builtin_type_int : Format.formatter -> builtin_type_int -> unit +val pp_map_key_type : Format.formatter -> map_key_type -> unit +val pp_builtin_type : Format.formatter -> builtin_type -> unit +val pp_unresolved : Format.formatter -> unresolved -> unit +val pp_resolved : Format.formatter -> resolved -> unit + +val pp_type : + (Format.formatter -> 'a -> unit) -> + Format.formatter -> + [ builtin_type | `User_defined of 'a ] -> + unit + +val pp_unresolved_t : + Format.formatter -> [ builtin_type | `User_defined of unresolved ] -> unit + +val pp_resolved_t : + Format.formatter -> [ builtin_type | `User_defined of resolved ] -> unit diff --git a/src/compilerlib/pb_option.ml b/src/compilerlib/pb_option.ml index d1ff42d8..dccdb611 100644 --- a/src/compilerlib/pb_option.ml +++ b/src/compilerlib/pb_option.ml @@ -17,3 +17,18 @@ let get t option_name = match List.assoc option_name t with | c -> Some c | exception Not_found -> None + +let pp_constant ppf = function + | Constant_string s -> Format.fprintf ppf "\"%s\"" s + | Constant_bool b -> Format.fprintf ppf "%B" b + | Constant_int i -> Format.fprintf ppf "%d" i + | Constant_float f -> Format.fprintf ppf "%f" f + | Constant_litteral l -> Format.fprintf ppf "`%s`" l + +let pp_t ppf (name, const) = + Format.fprintf ppf "{@;<1 2>%S: %a@;<1 2>}" name pp_constant const + +let pp_set ppf set = + Format.fprintf ppf "[@[%a@]]" + (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ ") pp_t) + set diff --git a/src/compilerlib/pb_option.mli b/src/compilerlib/pb_option.mli index d0f21f9f..406f670c 100644 --- a/src/compilerlib/pb_option.mli +++ b/src/compilerlib/pb_option.mli @@ -28,3 +28,6 @@ val merge : set -> set -> set than in case of duplicates [s2] options will override [s1] options. *) val get : set -> string -> constant option +val pp_constant : Format.formatter -> constant -> unit +val pp_t : Format.formatter -> t -> unit +val pp_set : Format.formatter -> set -> unit diff --git a/src/compilerlib/pb_parsing_parse_tree.ml b/src/compilerlib/pb_parsing_parse_tree.ml index 2c41755f..d440d2da 100644 --- a/src/compilerlib/pb_parsing_parse_tree.ml +++ b/src/compilerlib/pb_parsing_parse_tree.ml @@ -164,3 +164,170 @@ type proto = { } (** Definition of a protobuffer message file. *) + +open Format + +let pp_none ppf () = fprintf ppf "(None)" + +let pp_message_field_label ppf label = + let label_str = + match label with + | `Optional -> "Optional" + | `Required -> "Required" + | `Repeated -> "Repeated" + | `Nolabel -> "Nolabel" + in + fprintf ppf "`%s" label_str + +let pp_oneof_field_label _ppf () = () + +let pp_field pp_label ppf field = + fprintf ppf + "{@[<2>@ field_name = \"%s\";@ field_number = %d;@ field_label = %a;@ \ + field_type = %a;@ field_options = %a;@ }@]" + field.field_name field.field_number pp_label field.field_label + Pb_field_type.pp_unresolved_t field.field_type Pb_option.pp_set + field.field_options + +let pp_message_field ppf field = pp_field pp_message_field_label ppf field +let pp_oneof_field ppf field = pp_field pp_oneof_field_label ppf field + +let pp_map_field ppf map_field = + fprintf ppf + "{@[<2>@,\ + map_name = \"%s\";@,\ + map_number = %d;@,\ + map_key_type = %a;@,\ + map_value_type = %a;@,\ + map_options = %a;@,\ + }@]" + map_field.map_name map_field.map_number Pb_field_type.pp_map_key_type + map_field.map_key_type Pb_field_type.pp_unresolved_t + map_field.map_value_type Pb_option.pp_set map_field.map_options + +let pp_oneof ppf oneof = + fprintf ppf "{@[<2>@,oneof_name = \"%s\";@,oneof_fields = [@[%a@]];@,}@]" + oneof.oneof_name + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_oneof_field) + oneof.oneof_fields + +let pp_enum_value ppf enum_value = + fprintf ppf "{@[<2>@ enum_value_name = \"%s\";@ enum_value_int = %d;@ }@]" + enum_value.enum_value_name enum_value.enum_value_int + +let pp_enum_body_content ppf enum_body_content = + match enum_body_content with + | Enum_value enum_value -> pp_enum_value ppf enum_value + | Enum_option option -> Pb_option.pp_t ppf option + +let pp_enum ppf enum = + fprintf ppf + "{@[<2>@ enum_id = %d;@ enum_name = \"%s\";@ enum_body = [@[%a@]];@ }@]" + enum.enum_id enum.enum_name + (pp_print_list + ~pp_sep:(fun ppf () -> fprintf ppf ";@,") + pp_enum_body_content) + enum.enum_body + +let pp_extension_range_to ppf ext_range_to = + match ext_range_to with + | To_max -> fprintf ppf "To_max" + | To_number n -> fprintf ppf "(To_number %d)" n + +let pp_extension_range_from ppf ext_range_from = fprintf ppf "%d" ext_range_from + +let pp_extension_range ppf ext_range = + match ext_range with + | Extension_single_number n -> fprintf ppf "(Extension_single_number %d)" n + | Extension_range (from, to_) -> + fprintf ppf "(Extension_range (%d, %a))" from pp_extension_range_to to_ + +let rec pp_message_body_content ppf msg_body_content = + match msg_body_content with + | Message_field field -> pp_message_field ppf field + | Message_map_field map_field -> pp_map_field ppf map_field + | Message_oneof_field oneof_field -> pp_oneof ppf oneof_field + | Message_sub sub_message -> pp_message ppf sub_message + | Message_enum enum -> pp_enum ppf enum + | Message_extension ext_ranges -> + fprintf ppf "Message_extension [@[%a@]]" + (pp_print_list + ~pp_sep:(fun ppf () -> fprintf ppf ";@,") + pp_extension_range) + ext_ranges + | Message_reserved res_ranges -> + fprintf ppf "Message_reserved [@[%a@]]" + (pp_print_list + ~pp_sep:(fun ppf () -> fprintf ppf ";@,") + pp_extension_range) + res_ranges + | Message_option option -> Pb_option.pp_t ppf option + +and pp_message ppf message = + fprintf ppf + "{@[<2>@ id = %d;@ message_name = \"%s\";@ message_body = [@[%a@]];@ }@]" + message.id message.message_name + (pp_print_list + ~pp_sep:(fun ppf () -> fprintf ppf ";@,") + pp_message_body_content) + message.message_body + +let pp_rpc ppf rpc = + fprintf ppf + "{@[<2>@,\ + rpc_name = \"%s\";@,\ + rpc_options = %a;@,\ + rpc_req_stream = %b;@,\ + rpc_req = %a;@,\ + rpc_res_stream = %b;@,\ + rpc_res = %a;@,\ + }@]" + rpc.rpc_name Pb_option.pp_set rpc.rpc_options rpc.rpc_req_stream + Pb_field_type.pp_unresolved_t rpc.rpc_req rpc.rpc_res_stream + Pb_field_type.pp_unresolved_t rpc.rpc_res + +let rec pp_service_body_content ppf service_body_content = + match service_body_content with + | Service_rpc rpc -> pp_rpc ppf rpc + | Service_option option -> Pb_option.pp_t ppf option + +and pp_service ppf service = + fprintf ppf "{@[<2>@,service_name = \"%s\";@,service_body = [@[%a@]];@,}@]" + service.service_name + (pp_print_list + ~pp_sep:(fun ppf () -> fprintf ppf ";@,") + pp_service_body_content) + service.service_body + +let pp_extend ppf extend = + fprintf ppf + "{@[<2>@,id = %d;@,extend_name = \"%s\";@,extend_body = [@[%a@]];@,}@]" + extend.id extend.extend_name + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_message_field) + extend.extend_body + +let pp_import ppf import = + fprintf ppf "{@[<2>@,file_name = \"%s\";@,public = %b;@,}@]" import.file_name + import.public + +let pp_proto ppf proto = + fprintf ppf + "{@[<2>@ proto_file_name = %a;@ syntax = %a;@ imports = [@[%a@]];@ \ + file_options = %a;@ package = %a;@ messages = [@[%a@]];@ services = \ + [@[%a@]];@ enums = [@[%a@]];@ extends = [@[%a@]];@ }@]" + (pp_print_option ~none:pp_none pp_print_string) + proto.proto_file_name + (pp_print_option ~none:pp_none pp_print_string) + proto.syntax + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_import) + proto.imports Pb_option.pp_set proto.file_options + (pp_print_option ~none:pp_none pp_print_string) + proto.package + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_message) + proto.messages + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_service) + proto.services + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_enum) + proto.enums + (pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@,") pp_extend) + proto.extends diff --git a/src/tests/expectation/dune b/src/tests/expectation/dune new file mode 100644 index 00000000..9d66fcbc --- /dev/null +++ b/src/tests/expectation/dune @@ -0,0 +1,14 @@ +(executable + (name tests) + (libraries pbrt ocaml-protoc.compiler-lib) + (flags :standard -open Ocaml_protoc_compiler_lib)) + +(rule + (with-stdout-to + tests.output + (run ./tests.exe))) + +(rule + (alias runtest) + (action + (diff tests.expected tests.output))) diff --git a/src/tests/expectation/tests.expected b/src/tests/expectation/tests.expected new file mode 100644 index 00000000..35e392f2 --- /dev/null +++ b/src/tests/expectation/tests.expected @@ -0,0 +1,154 @@ +====================== ====================== + + syntax = "proto3"; + + message SimpleMessage { + int32 id = 1; + string name = 2; + bool active = 3; + } + +===================== ====================== +======================= ======================= +{ proto_file_name = test.proto; syntax = proto3; imports = []; + file_options = []; package = (None); + messages = [{ id = 1; message_name = "SimpleMessage"; + message_body = [{ field_name = "id"; field_number = 1; + field_label = `Nolabel; + field_type = "Int32"; + field_options = []; }; + { field_name = "name"; field_number = 2; + field_label = `Nolabel; + field_type = "String"; + field_options = []; }; + { field_name = "active"; field_number = 3; + field_label = `Nolabel; + field_type = "Bool"; field_options = [ + ]; }]; + }]; + services = []; enums = []; extends = []; } +====================== ======================= + + +====================== ====================== + + syntax = "proto3"; + + message NestedMessage { + int32 id = 1; + string description = 2; + } + + message ComplexMessage { + string title = 1; + repeated NestedMessage items = 2; + } + +===================== ====================== +======================= ======================= +{ proto_file_name = test.proto; syntax = proto3; imports = []; + file_options = []; package = (None); + messages = [{ id = 2; message_name = "NestedMessage"; + message_body = [{ field_name = "id"; field_number = 1; + field_label = `Nolabel; + field_type = "Int32"; + field_options = []; }; + { field_name = "description"; + field_number = 2; + field_label = `Nolabel; + field_type = "String"; + field_options = []; }]; + }; + { id = 3; message_name = "ComplexMessage"; + message_body = [{ field_name = "title"; field_number = 1; + field_label = `Nolabel; + field_type = "String"; + field_options = []; }; + { field_name = "items"; field_number = 2; + field_label = `Repeated; + field_type = { "type_path": ; + "type_name": "NestedMessage"; + "from_root": false }; + field_options = []; }]; + }]; + services = []; enums = []; extends = []; } +====================== ======================= + + +====================== ====================== + + syntax = "proto3"; + + enum Status { + UNKNOWN = 0; + ACTIVE = 1; + INACTIVE = 2; + } + + message SubMessage { + string content = 1; + } + + message DataMessage { + oneof data { + string text = 1; + int32 number = 2; + SubMessage sub = 3; + } + Status status = 4; + } + +===================== ====================== +======================= ======================= +{ proto_file_name = test.proto; syntax = proto3; imports = []; + file_options = []; package = (None); + messages = [{ id = 5; message_name = "SubMessage"; + message_body = [{ field_name = "content"; field_number = 1; + field_label = `Nolabel; + field_type = "String"; + field_options = []; }]; + }; + { id = 6; message_name = "DataMessage"; + message_body = [{oneof_name = "data"; + oneof_fields = [{ field_name = "text"; + field_number = 1; + field_label = ; + field_type = "String"; + field_options = [ + ]; }; + { field_name = "number"; + field_number = 2; + field_label = ; + field_type = "Int32"; + field_options = [ + ]; }; + { field_name = "sub"; + field_number = 3; + field_label = ; + field_type = { + "type_path": ; + "type_name": "SubMessage"; + "from_root": false }; + field_options = [ + ]; }]; + }; + { field_name = "status"; field_number = 4; + field_label = `Nolabel; + field_type = { "type_path": ; + "type_name": "Status"; + "from_root": false }; + field_options = []; }]; + }]; + services = []; + enums = [{ enum_id = 4; enum_name = "Status"; + enum_body = [{ enum_value_name = "UNKNOWN"; + enum_value_int = 0; }; + { enum_value_name = "ACTIVE"; enum_value_int = 1; + }; + { enum_value_name = "INACTIVE"; + enum_value_int = 2; }]; + }]; + extends = []; } +====================== ======================= + + diff --git a/src/tests/expectation/tests.ml b/src/tests/expectation/tests.ml new file mode 100644 index 00000000..5e277275 --- /dev/null +++ b/src/tests/expectation/tests.ml @@ -0,0 +1,67 @@ +module E = Pb_exception +module Pt = Pb_parsing_parse_tree + +let run proto = + let protos = Pb_parsing.parse_file (fun _ -> "test.proto", proto) "" in + let ppf = Format.std_formatter in + Format.fprintf ppf + "====================== \ + ======================@.%a@.===================== \ + ======================@.======================= \ + =======================@.%a@.====================== \ + =======================@.@.@." + Format.pp_print_string proto + (Format.pp_print_list + ~pp_sep:(fun ppf () -> Format.fprintf ppf "@.") + Pt.pp_proto) + protos + +let test_cases = + [ + {| + syntax = "proto3"; + + message SimpleMessage { + int32 id = 1; + string name = 2; + bool active = 3; + } +|}; + {| + syntax = "proto3"; + + message NestedMessage { + int32 id = 1; + string description = 2; + } + + message ComplexMessage { + string title = 1; + repeated NestedMessage items = 2; + } +|}; + {| + syntax = "proto3"; + + enum Status { + UNKNOWN = 0; + ACTIVE = 1; + INACTIVE = 2; + } + + message SubMessage { + string content = 1; + } + + message DataMessage { + oneof data { + string text = 1; + int32 number = 2; + SubMessage sub = 3; + } + Status status = 4; + } +|}; + ] + +let () = List.iter run test_cases