diff --git a/README.md b/README.md index 052a829..8940edf 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ the server. `gRPCStatus` represents the status of a request. It has the following fields: - `success`: whether the request was completed successfully. +- `grpc_status`: the grpc status code returned - `message`: any error message if request was not successful ### `gRPCCheck` @@ -227,6 +228,7 @@ A `gRPMessageTooLargeException` has the following members: A `gRPCServiceCallException` is thrown if a gRPC request is not successful. It has the following members: +- `grpc_status`: grpc status code for this request - `message`: any error message if request was not successful ## Credits diff --git a/src/curl.jl b/src/curl.jl index 5edc397..4f55689 100644 --- a/src/curl.jl +++ b/src/curl.jl @@ -1,5 +1,29 @@ const GRPC_STATIC_HEADERS = Ref{Ptr{Nothing}}(C_NULL) +const StatusCode = ( + OK = (code=0, message="Success"), + CANCELLED = (code=1, message="The operation was cancelled"), + UNKNOWN = (code=2, message="Unknown error"), + INVALID_ARGUMENT = (code=3, message="Client specified an invalid argument"), + DEADLINE_EXCEEDED = (code=4, message="Deadline expired before the operation could complete"), + NOT_FOUND = (code=5, message="Requested entity was not found"), + ALREADY_EXISTS = (code=6, message="Entity already exists"), + PERMISSION_DENIED = (code=7, message="No permission to execute the specified operation"), + RESOURCE_EXHAUSTED = (code=8, message="Resource exhausted"), + FAILED_PRECONDITION = (code=9, message="Operation was rejected because the system is not in a state required for the operation's execution"), + ABORTED = (code=10, message="Operation was aborted"), + OUT_OF_RANGE = (code=11, message="Operation was attempted past the valid range"), + UNIMPLEMENTED = (code=12, message="Operation is not implemented or is not supported/enabled in this service"), + INTERNAL = (code=13, message="Internal error"), + UNAVAILABLE = (code=14, message="The service is currently unavailable"), + DATA_LOSS = (code=15, message="Unrecoverable data loss or corruption"), + UNAUTHENTICATED = (code=16, message="The request does not have valid authentication credentials for the operation") +) + +grpc_status_info(code) = StatusCode[code+1] +grpc_status_message(code) = (grpc_status_info(code)).message +grpc_status_code_str(code) = string(propertynames(StatusCode)[code+1]) + #= const SEND_BUFFER_SZ = 1024 * 1024 function buffer_send_data(input::Channel{T}) where T <: ProtoType @@ -31,15 +55,46 @@ function send_data(easy::Curl.Easy, input::Channel{T}, max_send_message_length:: end end -function grpc_headers() +function grpc_timeout_header_val(timeout::Real) + if round(Int, timeout) == timeout + timeout_secs = round(Int64, timeout) + return "$(timeout_secs)S" + end + timeout *= 1000 + if round(Int, timeout) == timeout + timeout_millisecs = round(Int64, timeout) + return "$(timeout_millisecs)m" + end + timeout *= 1000 + if round(Int, timeout) == timeout + timeout_microsecs = round(Int64, timeout) + return "$(timeout_microsecs)u" + end + timeout *= 1000 + timeout_nanosecs = round(Int64, timeout) + return "$(timeout_nanosecs)n" +end + +function grpc_headers(; timeout::Real=Inf) headers = C_NULL headers = LibCURL.curl_slist_append(headers, "User-Agent: $(Curl.USER_AGENT)") headers = LibCURL.curl_slist_append(headers, "Content-Type: application/grpc+proto") headers = LibCURL.curl_slist_append(headers, "Content-Length:") + if timeout !== Inf + headers = LibCURL.curl_slist_append(headers, "grpc-timeout: $(grpc_timeout_header_val(timeout))") + end headers end -function easy_handle(maxage::Clong, keepalive::Clong, negotiation::Symbol, revocation::Bool) +function grpc_request_header(request_timeout::Real) + if request_timeout == Inf + GRPC_STATIC_HEADERS[] + else + grpc_headers(; timeout=request_timeout) + end +end + +function easy_handle(maxage::Clong, keepalive::Clong, negotiation::Symbol, revocation::Bool, request_timeout::Real) easy = Curl.Easy() http_version = (negotiation === :http2) ? CURL_HTTP_VERSION_2_0 : (negotiation === :http2_tls) ? CURL_HTTP_VERSION_2TLS : @@ -48,7 +103,7 @@ function easy_handle(maxage::Clong, keepalive::Clong, negotiation::Symbol, revoc Curl.setopt(easy, CURLOPT_HTTP_VERSION, http_version) Curl.setopt(easy, CURLOPT_PIPEWAIT, Clong(1)) Curl.setopt(easy, CURLOPT_POST, Clong(1)) - Curl.setopt(easy, CURLOPT_HTTPHEADER, GRPC_STATIC_HEADERS[]) + Curl.setopt(easy, CURLOPT_HTTPHEADER, grpc_request_header(request_timeout)) if !revocation Curl.setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) end @@ -126,7 +181,7 @@ function grpc_request(downloader::Downloader, url::String, input::Channel{T1}, o max_recv_message_length::Int = DEFAULT_MAX_RECV_MESSAGE_LENGTH, max_send_message_length::Int = DEFAULT_MAX_SEND_MESSAGE_LENGTH, verbose::Bool = false)::gRPCStatus where {T1 <: ProtoType, T2 <: ProtoType} - Curl.with_handle(easy_handle(maxage, keepalive, negotiation, revocation)) do easy + Curl.with_handle(easy_handle(maxage, keepalive, negotiation, revocation, request_timeout)) do easy # setup the request Curl.set_url(easy, url) Curl.set_timeout(easy, request_timeout) @@ -188,6 +243,29 @@ function grpc_request(downloader::Downloader, url::String, input::Channel{T1}, o end end - (easy.code == CURLE_OK) ? gRPCStatus(true, "") : gRPCStatus(false, Curl.get_curl_errstr(easy)) + @debug("response headers", easy.res_hdrs) + + # parse the grpc headers + grpc_status = StatusCode.OK.code + grpc_message = "" + for hdr in easy.res_hdrs + if startswith(hdr, "grpc-status") + grpc_status = parse(Int, strip(last(split(hdr, ':'; limit=2)))) + elseif startswith(hdr, "grpc-message") + grpc_message = string(strip(last(split(hdr, ':'; limit=2)))) + end + end + if (easy.code == CURLE_OPERATION_TIMEDOUT) && (grpc_status == StatusCode.OK.code) + grpc_status = StatusCode.DEADLINE_EXCEEDED.code + end + if (grpc_status != StatusCode.OK.code) && isempty(grpc_message) + grpc_message = grpc_status_message(grpc_status) + end + + if ((easy.code == CURLE_OK) && (grpc_status == StatusCode.OK.code)) + gRPCStatus(true, grpc_status, "") + else + gRPCStatus(false, grpc_status, isempty(grpc_message) ? Curl.get_curl_errstr(easy) : grpc_message) + end end end diff --git a/src/gRPCClient.jl b/src/gRPCClient.jl index a44a13b..c0878b5 100644 --- a/src/gRPCClient.jl +++ b/src/gRPCClient.jl @@ -7,7 +7,7 @@ using ProtoBuf import Downloads: Curl import ProtoBuf: call_method -export gRPCController, gRPCChannel, gRPCException, gRPCServiceCallException, gRPCMessageTooLargeException, gRPCStatus, gRPCCheck +export gRPCController, gRPCChannel, gRPCException, gRPCServiceCallException, gRPCMessageTooLargeException, gRPCStatus, gRPCCheck, StatusCode abstract type gRPCException <: Exception end diff --git a/src/grpc.jl b/src/grpc.jl index fee22c1..98b0c96 100644 --- a/src/grpc.jl +++ b/src/grpc.jl @@ -11,11 +11,12 @@ """ struct gRPCStatus success::Bool + grpc_status::Int message::String exception::Union{Nothing,Exception} end -gRPCStatus(success::Bool, message::AbstractString) = gRPCStatus(success, string(message), nothing) +gRPCStatus(success::Bool, grpc_status::Int, message::AbstractString) = gRPCStatus(success, grpc_status, string(message), nothing) function gRPCStatus(status_future) try fetch(status_future) @@ -24,7 +25,7 @@ function gRPCStatus(status_future) while isa(task_exception, TaskFailedException) task_exception = task_exception.task.exception end - gRPCStatus(false, string(task_exception), task_exception) + gRPCStatus(false, StatusCode.INTERNAL.code, string(task_exception), task_exception) end end @@ -39,10 +40,11 @@ It has the following members: - `message`: any error message if request was not successful """ struct gRPCServiceCallException <: gRPCException + grpc_status::Int message::String end -Base.show(io::IO, m::gRPCServiceCallException) = print(io, "gRPCServiceCallException - $(m.message)") +Base.show(io::IO, m::gRPCServiceCallException) = print(io, "gRPCServiceCallException: $(m.grpc_status), $(m.message)") """ gRPCCheck(status; throw_error::Bool=true) @@ -56,7 +58,7 @@ gRPCCheck(status_future; throw_error::Bool=true) = gRPCCheck(gRPCStatus(status_f function gRPCCheck(status::gRPCStatus; throw_error::Bool=true) if throw_error && !status.success if status.exception === nothing - throw(gRPCServiceCallException(status.message)) + throw(gRPCServiceCallException(status.grpc_status, status.message)) else throw(status.exception) end @@ -180,8 +182,13 @@ function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::M outchannel, status_future = call_method(channel, service, method, controller, input, Channel{T2}()) try take!(outchannel), status_future - catch - nothing, status_future + catch ex + gRPCCheck(status_future) # check for core issue + if isa(ex, InvalidStateException) + throw(gRPCServiceCallException("Server closed connection without any response")) + else + rethrow() # throw this error if there's no other issue + end end end function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, input::Channel{T1}, outchannel::Channel{T2}) where {T1 <: ProtoType, T2 <: ProtoType} diff --git a/test/.gitignore b/test/.gitignore index 3513e3f..17bd12a 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1,5 @@ grpc-go server.pid -runserver_* +routeguide_* +grpcerrors_* +testservers diff --git a/test/GrpcerrorsClients/GrpcerrorsClients.jl b/test/GrpcerrorsClients/GrpcerrorsClients.jl new file mode 100644 index 0000000..f4625c2 --- /dev/null +++ b/test/GrpcerrorsClients/GrpcerrorsClients.jl @@ -0,0 +1,84 @@ +module GrpcerrorsClients +using gRPCClient + +include("grpcerrors.jl") +using .grpcerrors + +import Base: show + +# begin service: grpcerrors.GRPCErrors + +export GRPCErrorsBlockingClient, GRPCErrorsClient + +struct GRPCErrorsBlockingClient + controller::gRPCController + channel::gRPCChannel + stub::GRPCErrorsBlockingStub + + function GRPCErrorsBlockingClient(api_base_url::String; kwargs...) + controller = gRPCController(; kwargs...) + channel = gRPCChannel(api_base_url) + stub = GRPCErrorsBlockingStub(channel) + new(controller, channel, stub) + end +end + +struct GRPCErrorsClient + controller::gRPCController + channel::gRPCChannel + stub::GRPCErrorsStub + + function GRPCErrorsClient(api_base_url::String; kwargs...) + controller = gRPCController(; kwargs...) + channel = gRPCChannel(api_base_url) + stub = GRPCErrorsStub(channel) + new(controller, channel, stub) + end +end + +show(io::IO, client::GRPCErrorsBlockingClient) = print(io, "GRPCErrorsBlockingClient(", client.channel.baseurl, ")") +show(io::IO, client::GRPCErrorsClient) = print(io, "GRPCErrorsClient(", client.channel.baseurl, ")") + +import .grpcerrors: SimpleRPC +""" + SimpleRPC + +- input: grpcerrors.Data +- output: grpcerrors.Data +""" +SimpleRPC(client::GRPCErrorsBlockingClient, inp::grpcerrors.Data) = SimpleRPC(client.stub, client.controller, inp) +SimpleRPC(client::GRPCErrorsClient, inp::grpcerrors.Data, done::Function) = SimpleRPC(client.stub, client.controller, inp, done) + +import .grpcerrors: StreamResponse +""" + StreamResponse + +- input: grpcerrors.Data +- output: Channel{grpcerrors.Data} +""" +StreamResponse(client::GRPCErrorsBlockingClient, inp::grpcerrors.Data) = StreamResponse(client.stub, client.controller, inp) +StreamResponse(client::GRPCErrorsClient, inp::grpcerrors.Data, done::Function) = StreamResponse(client.stub, client.controller, inp, done) + +import .grpcerrors: StreamRequest +""" + StreamRequest + +- input: Channel{grpcerrors.Data} +- output: grpcerrors.Data +""" +StreamRequest(client::GRPCErrorsBlockingClient, inp::Channel{grpcerrors.Data}) = StreamRequest(client.stub, client.controller, inp) +StreamRequest(client::GRPCErrorsClient, inp::Channel{grpcerrors.Data}, done::Function) = StreamRequest(client.stub, client.controller, inp, done) + +import .grpcerrors: StreamRequestResponse +""" + StreamRequestResponse + +- input: Channel{grpcerrors.Data} +- output: Channel{grpcerrors.Data} +""" +StreamRequestResponse(client::GRPCErrorsBlockingClient, inp::Channel{grpcerrors.Data}) = StreamRequestResponse(client.stub, client.controller, inp) +StreamRequestResponse(client::GRPCErrorsClient, inp::Channel{grpcerrors.Data}, done::Function) = StreamRequestResponse(client.stub, client.controller, inp, done) + +# end service: grpcerrors.GRPCErrors + +end # module GrpcerrorsClients diff --git a/test/GrpcerrorsClients/grpcerrors.jl b/test/GrpcerrorsClients/grpcerrors.jl new file mode 100644 index 0000000..629b724 --- /dev/null +++ b/test/GrpcerrorsClients/grpcerrors.jl @@ -0,0 +1,4 @@ +module grpcerrors + const _ProtoBuf_Top_ = @static isdefined(parentmodule(@__MODULE__), :_ProtoBuf_Top_) ? (parentmodule(@__MODULE__))._ProtoBuf_Top_ : parentmodule(@__MODULE__) + include("grpcerrors_pb.jl") +end diff --git a/test/GrpcerrorsClients/grpcerrors_pb.jl b/test/GrpcerrorsClients/grpcerrors_pb.jl new file mode 100644 index 0000000..c48d331 --- /dev/null +++ b/test/GrpcerrorsClients/grpcerrors_pb.jl @@ -0,0 +1,77 @@ +# syntax: proto3 +using ProtoBuf +import ProtoBuf.meta + +mutable struct Data <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function Data(; kwargs...) + obj = new(meta(Data), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct Data +const __meta_Data = Ref{ProtoMeta}() +function meta(::Type{Data}) + ProtoBuf.metalock() do + if !isassigned(__meta_Data) + __meta_Data[] = target = ProtoMeta(Data) + allflds = Pair{Symbol,Union{Type,String}}[:mode => Int32, :param => Int32] + meta(target, Data, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) + end + __meta_Data[] + end +end +function Base.getproperty(obj::Data, name::Symbol) + if name === :mode + return (obj.__protobuf_jl_internal_values[name])::Int32 + elseif name === :param + return (obj.__protobuf_jl_internal_values[name])::Int32 + else + getfield(obj, name) + end +end + +# service methods for GRPCErrors +const _GRPCErrors_methods = MethodDescriptor[ + MethodDescriptor("SimpleRPC", 1, Data, Data), + MethodDescriptor("StreamResponse", 2, Data, Channel{Data}), + MethodDescriptor("StreamRequest", 3, Channel{Data}, Data), + MethodDescriptor("StreamRequestResponse", 4, Channel{Data}, Channel{Data}) + ] # const _GRPCErrors_methods +const _GRPCErrors_desc = ServiceDescriptor("grpcerrors.GRPCErrors", 1, _GRPCErrors_methods) + +GRPCErrors(impl::Module) = ProtoService(_GRPCErrors_desc, impl) + +mutable struct GRPCErrorsStub <: AbstractProtoServiceStub{false} + impl::ProtoServiceStub + GRPCErrorsStub(channel::ProtoRpcChannel) = new(ProtoServiceStub(_GRPCErrors_desc, channel)) +end # mutable struct GRPCErrorsStub + +mutable struct GRPCErrorsBlockingStub <: AbstractProtoServiceStub{true} + impl::ProtoServiceBlockingStub + GRPCErrorsBlockingStub(channel::ProtoRpcChannel) = new(ProtoServiceBlockingStub(_GRPCErrors_desc, channel)) +end # mutable struct GRPCErrorsBlockingStub + +SimpleRPC(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Data, done::Function) = call_method(stub.impl, _GRPCErrors_methods[1], controller, inp, done) +SimpleRPC(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Data) = call_method(stub.impl, _GRPCErrors_methods[1], controller, inp) + +StreamResponse(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Data, done::Function) = call_method(stub.impl, _GRPCErrors_methods[2], controller, inp, done) +StreamResponse(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Data) = call_method(stub.impl, _GRPCErrors_methods[2], controller, inp) + +StreamRequest(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Channel{Data}, done::Function) = call_method(stub.impl, _GRPCErrors_methods[3], controller, inp, done) +StreamRequest(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Channel{Data}) = call_method(stub.impl, _GRPCErrors_methods[3], controller, inp) + +StreamRequestResponse(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Channel{Data}, done::Function) = call_method(stub.impl, _GRPCErrors_methods[4], controller, inp, done) +StreamRequestResponse(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Channel{Data}) = call_method(stub.impl, _GRPCErrors_methods[4], controller, inp) + +export Data, GRPCErrors, GRPCErrorsStub, GRPCErrorsBlockingStub, SimpleRPC, StreamResponse, StreamRequest, StreamRequestResponse diff --git a/test/buildserver.sh b/test/buildserver.sh index 6c44953..586daf1 100755 --- a/test/buildserver.sh +++ b/test/buildserver.sh @@ -2,11 +2,16 @@ set -e BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd ${BASEDIR} - export PATH="$PATH:$(go env GOPATH)/bin" +mkdir -p ${BASEDIR}/testservers + +# build routeguide server +cd ${BASEDIR} +if [ ! -d "grpc-go" ] +then + git clone -b v1.35.0 https://github.com/grpc/grpc-go +fi -git clone -b v1.35.0 https://github.com/grpc/grpc-go cd grpc-go/examples/route_guide protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative routeguide/route_guide.proto sed 's/localhost/0.0.0.0/g' server/server.go > server/server.go.new @@ -15,22 +20,46 @@ mv server/server.go.new server/server.go export GOOS=linux export GOARCH=amd64 -echo "building runserver_${GOOS}_${GOARCH}..." -go build -o runserver_${GOOS}_${GOARCH} -i server/server.go +echo "building routeguide_${GOOS}_${GOARCH}..." +go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go +export GOARCH=386 +echo "building routeguide_${GOOS}_${GOARCH}..." +go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go +export GOOS=windows +export GOARCH=amd64 +echo "building routeguide_${GOOS}_${GOARCH}..." +go build -o routeguide_${GOOS}_${GOARCH}.exe -i server/server.go +export GOARCH=386 +echo "building routeguide_${GOOS}_${GOARCH}..." +go build -o routeguide_${GOOS}_${GOARCH}.exe -i server/server.go +export GOOS=darwin +export GOARCH=amd64 +echo "building routeguide_${GOOS}_${GOARCH}..." +go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go + +cp routeguide_* ${BASEDIR}/testservers/ + +# build grpcerrors server +cd ${BASEDIR} +cd error_test_server + +export GOOS=linux +export GOARCH=amd64 +echo "building grpcerrors_${GOOS}_${GOARCH}..." +go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go export GOARCH=386 -echo "building runserver_${GOOS}_${GOARCH}..." -go build -o runserver_${GOOS}_${GOARCH} -i server/server.go +echo "building grpcerrors_${GOOS}_${GOARCH}..." +go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go export GOOS=windows export GOARCH=amd64 -echo "building runserver_${GOOS}_${GOARCH}..." -go build -o runserver_${GOOS}_${GOARCH}.exe -i server/server.go +echo "building grpcerrors_${GOOS}_${GOARCH}..." +go build -o grpcerrors_${GOOS}_${GOARCH}.exe -i server.go export GOARCH=386 -echo "building runserver_${GOOS}_${GOARCH}..." -go build -o runserver_${GOOS}_${GOARCH}.exe -i server/server.go +echo "building grpcerrors_${GOOS}_${GOARCH}..." +go build -o grpcerrors_${GOOS}_${GOARCH}.exe -i server.go export GOOS=darwin export GOARCH=amd64 -echo "building runserver_${GOOS}_${GOARCH}..." -go build -o runserver_${GOOS}_${GOARCH} -i server/server.go +echo "building grpcerrors_${GOOS}_${GOARCH}..." +go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go -mkdir -p ${BASEDIR}/runserver -cp runserver_* ${BASEDIR}/runserver/ +cp grpcerrors_* ${BASEDIR}/testservers/ diff --git a/test/error_test_server/.gitignore b/test/error_test_server/.gitignore new file mode 100644 index 0000000..e9fe830 --- /dev/null +++ b/test/error_test_server/.gitignore @@ -0,0 +1 @@ +grpcerrors_* diff --git a/test/error_test_server/client.go b/test/error_test_server/client.go new file mode 100644 index 0000000..c376f1b --- /dev/null +++ b/test/error_test_server/client.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "flag" + "log" + "time" + + "google.golang.org/grpc" + pb "juliacomputing.com/errortest/grpcerrors" +) + +var ( + tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") + caFile = flag.String("ca_file", "", "The file containing the CA root cert file") + serverAddr = flag.String("server_addr", "localhost:10000", "The server address in the format of host:port") + serverHostOverride = flag.String("server_host_override", "x.test.youtube.com", "The server name used to verify the hostname returned by the TLS handshake") +) + +func simpleRPC(client pb.GRPCErrorsClient, data *pb.Data) { + log.Printf("Calling simpleRPC for data (%d, %d)", data.Mode, data.Param) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + respdata, err := client.SimpleRPC(ctx, data) + if err != nil { + log.Fatalf("%v.SimpleRPC(_) = _, %v: ", client, err) + } + log.Println(respdata) +} + +func main() { + flag.Parse() + var opts []grpc.DialOption + opts = append(opts, grpc.WithInsecure()) + opts = append(opts, grpc.WithBlock()) + conn, err := grpc.Dial(*serverAddr, opts...) + if err != nil { + log.Fatalf("fail to dial: %v", err) + } + defer conn.Close() + client := pb.NewGRPCErrorsClient(conn) + + // simpel RPC + simpleRPC(client, &pb.Data{Mode: 1, Param: 0}) +} + diff --git a/test/error_test_server/go.mod b/test/error_test_server/go.mod new file mode 100644 index 0000000..83c3ebf --- /dev/null +++ b/test/error_test_server/go.mod @@ -0,0 +1,12 @@ +module juliacomputing.com/errortest + +go 1.11 + +require ( + github.com/golang/protobuf v1.5.2 + golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect + golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect + google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b // indirect + google.golang.org/grpc v1.37.0 + google.golang.org/protobuf v1.26.0 +) diff --git a/test/error_test_server/go.sum b/test/error_test_server/go.sum new file mode 100644 index 0000000..13b45f4 --- /dev/null +++ b/test/error_test_server/go.sum @@ -0,0 +1,121 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b h1:Rt15zyw7G2yfLqmsjEa1xICjWEw+topkn7vEAR6bVPk= +google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test/error_test_server/grpcerrors/grpcerrors.pb.go b/test/error_test_server/grpcerrors/grpcerrors.pb.go new file mode 100644 index 0000000..4f51439 --- /dev/null +++ b/test/error_test_server/grpcerrors/grpcerrors.pb.go @@ -0,0 +1,183 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.6.1 +// source: grpcerrors.proto + +package grpcerrors + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Request parameter, dictates how the simulation should behave. +// mode can take values: +// 1: throw an error after seconds provided in `param` +// 2: no error, just wait until seconds provided in `param`, respond with SimulationParams +// +// when sent in a stream as input, the server would consider only the first one in the +// stream to determine the course of action +type Data struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mode int32 `protobuf:"varint,1,opt,name=mode,proto3" json:"mode,omitempty"` + Param int32 `protobuf:"varint,2,opt,name=param,proto3" json:"param,omitempty"` +} + +func (x *Data) Reset() { + *x = Data{} + if protoimpl.UnsafeEnabled { + mi := &file_grpcerrors_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Data) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Data) ProtoMessage() {} + +func (x *Data) ProtoReflect() protoreflect.Message { + mi := &file_grpcerrors_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Data.ProtoReflect.Descriptor instead. +func (*Data) Descriptor() ([]byte, []int) { + return file_grpcerrors_proto_rawDescGZIP(), []int{0} +} + +func (x *Data) GetMode() int32 { + if x != nil { + return x.Mode + } + return 0 +} + +func (x *Data) GetParam() int32 { + if x != nil { + return x.Param + } + return 0 +} + +var File_grpcerrors_proto protoreflect.FileDescriptor + +var file_grpcerrors_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x0a, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x30, + 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x32, 0xf5, 0x01, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, + 0x31, 0x0a, 0x09, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x50, 0x43, 0x12, 0x10, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x10, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, + 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x00, 0x30, 0x01, 0x12, 0x37, 0x0a, 0x0d, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, + 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x22, 0x00, 0x28, 0x01, 0x12, 0x41, 0x0a, 0x15, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, + 0x1a, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x1f, 0x5a, 0x1d, 0x6a, 0x75, 0x6c, 0x69, + 0x61, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_grpcerrors_proto_rawDescOnce sync.Once + file_grpcerrors_proto_rawDescData = file_grpcerrors_proto_rawDesc +) + +func file_grpcerrors_proto_rawDescGZIP() []byte { + file_grpcerrors_proto_rawDescOnce.Do(func() { + file_grpcerrors_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpcerrors_proto_rawDescData) + }) + return file_grpcerrors_proto_rawDescData +} + +var file_grpcerrors_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_grpcerrors_proto_goTypes = []interface{}{ + (*Data)(nil), // 0: grpcerrors.Data +} +var file_grpcerrors_proto_depIdxs = []int32{ + 0, // 0: grpcerrors.GRPCErrors.SimpleRPC:input_type -> grpcerrors.Data + 0, // 1: grpcerrors.GRPCErrors.StreamResponse:input_type -> grpcerrors.Data + 0, // 2: grpcerrors.GRPCErrors.StreamRequest:input_type -> grpcerrors.Data + 0, // 3: grpcerrors.GRPCErrors.StreamRequestResponse:input_type -> grpcerrors.Data + 0, // 4: grpcerrors.GRPCErrors.SimpleRPC:output_type -> grpcerrors.Data + 0, // 5: grpcerrors.GRPCErrors.StreamResponse:output_type -> grpcerrors.Data + 0, // 6: grpcerrors.GRPCErrors.StreamRequest:output_type -> grpcerrors.Data + 0, // 7: grpcerrors.GRPCErrors.StreamRequestResponse:output_type -> grpcerrors.Data + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_grpcerrors_proto_init() } +func file_grpcerrors_proto_init() { + if File_grpcerrors_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_grpcerrors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Data); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_grpcerrors_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_grpcerrors_proto_goTypes, + DependencyIndexes: file_grpcerrors_proto_depIdxs, + MessageInfos: file_grpcerrors_proto_msgTypes, + }.Build() + File_grpcerrors_proto = out.File + file_grpcerrors_proto_rawDesc = nil + file_grpcerrors_proto_goTypes = nil + file_grpcerrors_proto_depIdxs = nil +} diff --git a/test/error_test_server/grpcerrors/grpcerrors.proto b/test/error_test_server/grpcerrors/grpcerrors.proto new file mode 100644 index 0000000..d53aaa9 --- /dev/null +++ b/test/error_test_server/grpcerrors/grpcerrors.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +option go_package = "juliacomputing.com/grpcerrors"; + +package grpcerrors; + +// Interface exported by the server. +service GRPCErrors { + // simple RPC, takes a message and responds with a message + rpc SimpleRPC(Data) returns (Data) {} + + // streaming response, takes a message and responds with a stream + rpc StreamResponse(Data) returns (stream Data) {} + + // streaming request, takes streaming input and responds with a message + rpc StreamRequest(stream Data) returns (Data) {} + + // streaming request and response + rpc StreamRequestResponse(stream Data) returns (stream Data) {} +} + +// Request parameter, dictates how the simulation should behave. +// mode can take values: +// 1: throw an error after seconds provided in `param` +// 2: no error, just wait until seconds provided in `param`, respond with SimulationParams +// +// when sent in a stream as input, the server would consider only the first one in the +// stream to determine the course of action +message Data { + int32 mode = 1; + int32 param = 2; +} diff --git a/test/error_test_server/grpcerrors/grpcerrors_grpc.pb.go b/test/error_test_server/grpcerrors/grpcerrors_grpc.pb.go new file mode 100644 index 0000000..774bb7a --- /dev/null +++ b/test/error_test_server/grpcerrors/grpcerrors_grpc.pb.go @@ -0,0 +1,311 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package grpcerrors + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GRPCErrorsClient is the client API for GRPCErrors service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GRPCErrorsClient interface { + // simple RPC, takes a message and responds with a message + SimpleRPC(ctx context.Context, in *Data, opts ...grpc.CallOption) (*Data, error) + // streaming response, takes a message and responds with a stream + StreamResponse(ctx context.Context, in *Data, opts ...grpc.CallOption) (GRPCErrors_StreamResponseClient, error) + // streaming request, takes streaming input and responds with a message + StreamRequest(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestClient, error) + // streaming request and response + StreamRequestResponse(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestResponseClient, error) +} + +type gRPCErrorsClient struct { + cc grpc.ClientConnInterface +} + +func NewGRPCErrorsClient(cc grpc.ClientConnInterface) GRPCErrorsClient { + return &gRPCErrorsClient{cc} +} + +func (c *gRPCErrorsClient) SimpleRPC(ctx context.Context, in *Data, opts ...grpc.CallOption) (*Data, error) { + out := new(Data) + err := c.cc.Invoke(ctx, "/grpcerrors.GRPCErrors/SimpleRPC", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gRPCErrorsClient) StreamResponse(ctx context.Context, in *Data, opts ...grpc.CallOption) (GRPCErrors_StreamResponseClient, error) { + stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[0], "/grpcerrors.GRPCErrors/StreamResponse", opts...) + if err != nil { + return nil, err + } + x := &gRPCErrorsStreamResponseClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GRPCErrors_StreamResponseClient interface { + Recv() (*Data, error) + grpc.ClientStream +} + +type gRPCErrorsStreamResponseClient struct { + grpc.ClientStream +} + +func (x *gRPCErrorsStreamResponseClient) Recv() (*Data, error) { + m := new(Data) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *gRPCErrorsClient) StreamRequest(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestClient, error) { + stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[1], "/grpcerrors.GRPCErrors/StreamRequest", opts...) + if err != nil { + return nil, err + } + x := &gRPCErrorsStreamRequestClient{stream} + return x, nil +} + +type GRPCErrors_StreamRequestClient interface { + Send(*Data) error + CloseAndRecv() (*Data, error) + grpc.ClientStream +} + +type gRPCErrorsStreamRequestClient struct { + grpc.ClientStream +} + +func (x *gRPCErrorsStreamRequestClient) Send(m *Data) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCErrorsStreamRequestClient) CloseAndRecv() (*Data, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(Data) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *gRPCErrorsClient) StreamRequestResponse(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestResponseClient, error) { + stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[2], "/grpcerrors.GRPCErrors/StreamRequestResponse", opts...) + if err != nil { + return nil, err + } + x := &gRPCErrorsStreamRequestResponseClient{stream} + return x, nil +} + +type GRPCErrors_StreamRequestResponseClient interface { + Send(*Data) error + Recv() (*Data, error) + grpc.ClientStream +} + +type gRPCErrorsStreamRequestResponseClient struct { + grpc.ClientStream +} + +func (x *gRPCErrorsStreamRequestResponseClient) Send(m *Data) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCErrorsStreamRequestResponseClient) Recv() (*Data, error) { + m := new(Data) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCErrorsServer is the server API for GRPCErrors service. +// All implementations must embed UnimplementedGRPCErrorsServer +// for forward compatibility +type GRPCErrorsServer interface { + // simple RPC, takes a message and responds with a message + SimpleRPC(context.Context, *Data) (*Data, error) + // streaming response, takes a message and responds with a stream + StreamResponse(*Data, GRPCErrors_StreamResponseServer) error + // streaming request, takes streaming input and responds with a message + StreamRequest(GRPCErrors_StreamRequestServer) error + // streaming request and response + StreamRequestResponse(GRPCErrors_StreamRequestResponseServer) error + mustEmbedUnimplementedGRPCErrorsServer() +} + +// UnimplementedGRPCErrorsServer must be embedded to have forward compatible implementations. +type UnimplementedGRPCErrorsServer struct { +} + +func (UnimplementedGRPCErrorsServer) SimpleRPC(context.Context, *Data) (*Data, error) { + return nil, status.Errorf(codes.Unimplemented, "method SimpleRPC not implemented") +} +func (UnimplementedGRPCErrorsServer) StreamResponse(*Data, GRPCErrors_StreamResponseServer) error { + return status.Errorf(codes.Unimplemented, "method StreamResponse not implemented") +} +func (UnimplementedGRPCErrorsServer) StreamRequest(GRPCErrors_StreamRequestServer) error { + return status.Errorf(codes.Unimplemented, "method StreamRequest not implemented") +} +func (UnimplementedGRPCErrorsServer) StreamRequestResponse(GRPCErrors_StreamRequestResponseServer) error { + return status.Errorf(codes.Unimplemented, "method StreamRequestResponse not implemented") +} +func (UnimplementedGRPCErrorsServer) mustEmbedUnimplementedGRPCErrorsServer() {} + +// UnsafeGRPCErrorsServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GRPCErrorsServer will +// result in compilation errors. +type UnsafeGRPCErrorsServer interface { + mustEmbedUnimplementedGRPCErrorsServer() +} + +func RegisterGRPCErrorsServer(s grpc.ServiceRegistrar, srv GRPCErrorsServer) { + s.RegisterService(&GRPCErrors_ServiceDesc, srv) +} + +func _GRPCErrors_SimpleRPC_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Data) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GRPCErrorsServer).SimpleRPC(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpcerrors.GRPCErrors/SimpleRPC", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GRPCErrorsServer).SimpleRPC(ctx, req.(*Data)) + } + return interceptor(ctx, in, info, handler) +} + +func _GRPCErrors_StreamResponse_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Data) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GRPCErrorsServer).StreamResponse(m, &gRPCErrorsStreamResponseServer{stream}) +} + +type GRPCErrors_StreamResponseServer interface { + Send(*Data) error + grpc.ServerStream +} + +type gRPCErrorsStreamResponseServer struct { + grpc.ServerStream +} + +func (x *gRPCErrorsStreamResponseServer) Send(m *Data) error { + return x.ServerStream.SendMsg(m) +} + +func _GRPCErrors_StreamRequest_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCErrorsServer).StreamRequest(&gRPCErrorsStreamRequestServer{stream}) +} + +type GRPCErrors_StreamRequestServer interface { + SendAndClose(*Data) error + Recv() (*Data, error) + grpc.ServerStream +} + +type gRPCErrorsStreamRequestServer struct { + grpc.ServerStream +} + +func (x *gRPCErrorsStreamRequestServer) SendAndClose(m *Data) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCErrorsStreamRequestServer) Recv() (*Data, error) { + m := new(Data) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _GRPCErrors_StreamRequestResponse_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCErrorsServer).StreamRequestResponse(&gRPCErrorsStreamRequestResponseServer{stream}) +} + +type GRPCErrors_StreamRequestResponseServer interface { + Send(*Data) error + Recv() (*Data, error) + grpc.ServerStream +} + +type gRPCErrorsStreamRequestResponseServer struct { + grpc.ServerStream +} + +func (x *gRPCErrorsStreamRequestResponseServer) Send(m *Data) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCErrorsStreamRequestResponseServer) Recv() (*Data, error) { + m := new(Data) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCErrors_ServiceDesc is the grpc.ServiceDesc for GRPCErrors service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GRPCErrors_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpcerrors.GRPCErrors", + HandlerType: (*GRPCErrorsServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SimpleRPC", + Handler: _GRPCErrors_SimpleRPC_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamResponse", + Handler: _GRPCErrors_StreamResponse_Handler, + ServerStreams: true, + }, + { + StreamName: "StreamRequest", + Handler: _GRPCErrors_StreamRequest_Handler, + ClientStreams: true, + }, + { + StreamName: "StreamRequestResponse", + Handler: _GRPCErrors_StreamRequestResponse_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpcerrors.proto", +} diff --git a/test/error_test_server/server.go b/test/error_test_server/server.go new file mode 100644 index 0000000..b868e15 --- /dev/null +++ b/test/error_test_server/server.go @@ -0,0 +1,125 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "io" + "log" + "net" + "time" + + "google.golang.org/grpc" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" + + pb "juliacomputing.com/errortest/grpcerrors" +) + +var ( + tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") + certFile = flag.String("cert_file", "", "The TLS cert file") + keyFile = flag.String("key_file", "", "The TLS key file") + port = flag.Int("port", 10000, "The server port") +) + +type gRPCErrorsServer struct { + pb.UnimplementedGRPCErrorsServer +} + +func (gRPCErrorsServer) SimpleRPC(ctx context.Context, data *pb.Data) (*pb.Data, error) { + if data.Mode == 1 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return data, errors.New("simulated error mode 1") + } else if data.Mode == 2 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return data, nil + } else { + return nil, status.Errorf(codes.Unimplemented, "mode not implemented") + } +} + +func (gRPCErrorsServer) StreamResponse(data *pb.Data, stream pb.GRPCErrors_StreamResponseServer) error { + if data.Mode == 1 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return errors.New("simulated error mode 1") + } else if data.Mode == 2 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + if err := stream.Send(data); err != nil { + return err + } + return nil + } else { + return status.Errorf(codes.Unimplemented, "mode not implemented") + } +} + +func (gRPCErrorsServer) StreamRequest(stream pb.GRPCErrors_StreamRequestServer) error { + data, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + if data.Mode == 1 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return errors.New("simulated error mode 1") + } else if data.Mode == 2 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return stream.SendAndClose(data) + } else { + return status.Errorf(codes.Unimplemented, "mode not implemented") + } +} + +func (gRPCErrorsServer) StreamRequestResponse(stream pb.GRPCErrors_StreamRequestResponseServer) error { + data, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + if data.Mode == 1 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + return errors.New("simulated error mode 1") + } else if data.Mode == 2 { + time.Sleep(time.Duration(int64(data.Param)) * time.Second) + if err := stream.Send(data); err != nil { + return err + } + return nil + } else { + return status.Errorf(codes.Unimplemented, "mode not implemented") + } +} + +func newServer() *gRPCErrorsServer { + s := &gRPCErrorsServer{} + return s +} + +func main() { + flag.Parse() + lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + var opts []grpc.ServerOption + if *tls { + creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) + if err != nil { + log.Fatalf("Failed to generate credentials %v", err) + } + opts = []grpc.ServerOption{grpc.Creds(creds)} + } + grpcServer := grpc.NewServer(opts...) + pb.RegisterGRPCErrorsServer(grpcServer, newServer()) + grpcServer.Serve(lis) +} diff --git a/test/runtests.jl b/test/runtests.jl index 032ddd5..2fbc674 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,80 +1,6 @@ -using gRPCClient -using Downloads -using Random -using Sockets using Test -const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.1/" -function server_binary() - arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" - filename = Sys.islinux() ? "runserver_linux_$(arch)" : - Sys.iswindows() ? "runserver_windows_$(arch).exe" : - Sys.isapple() ? "runserver_darwin_$(arch)" : - error("no server binary available for this platform") - source = string(SERVER_RELEASE, filename) - destination = joinpath(@__DIR__, filename) - isfile(destination) || Downloads.download(source, destination) - ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) - - destination -end - -function start_server() - serverbin = server_binary() - - @info("starting test server", serverbin) - serverproc = run(`$serverbin`; wait=false) - - listening = timedwait(120.0; pollint=5.0) do - try - sock = connect(ip"127.0.0.1", 10000) - close(sock) - true - catch - false - end - end - - if listening !== :ok - @warn("test server did not start, stopping server") - kill(serverproc) - error("test server did not start") - end - - serverproc -end - -function test_generate() - @testset "codegen" begin - dir = joinpath(@__DIR__, "RouteguideClients") - gRPCClient.generate(joinpath(dir, "route_guide.proto"); outdir=dir) - @test isfile(joinpath(dir, "route_guide_pb.jl")) - @test isfile(joinpath(dir, "routeguide.jl")) - @test isfile(joinpath(dir, "RouteguideClients.jl")) - end -end - -# switch off host verification for tests -if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) - ENV["JULIA_NO_VERIFY_HOSTS"] = "**" -end - -server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] -@info("server endpoint: $server_endpoint") - @testset "gRPCClient" begin - if !Sys.iswindows() - test_generate() - else - @info("skipping code generation on Windows to avoid needing batch file execution permissions") - end - include("test_routeclient.jl") - - serverproc = start_server() - - @debug("testing routeclinet...") - test_clients(server_endpoint) - - kill(serverproc) - @info("stopped test server") + include("runtests_routeguide.jl") + include("runtests_errors.jl") end diff --git a/test/runtests_errors.jl b/test/runtests_errors.jl new file mode 100644 index 0000000..1028106 --- /dev/null +++ b/test/runtests_errors.jl @@ -0,0 +1,83 @@ +module ErrorTest + +using gRPCClient +using Downloads +using Random +using Sockets +using Test + +const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.2/" +function server_binary() + arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" + filename = Sys.islinux() ? "grpcerrors_linux_$(arch)" : + Sys.iswindows() ? "grpcerrors_windows_$(arch).exe" : + Sys.isapple() ? "grpcerrors_darwin_$(arch)" : + error("no server binary available for this platform") + source = string(SERVER_RELEASE, filename) + destination = joinpath(@__DIR__, filename) + isfile(destination) || Downloads.download(source, destination) + ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) + + destination +end + +function start_server() + serverbin = server_binary() + + @info("starting test server", serverbin) + serverproc = run(`$serverbin`; wait=false) + + listening = timedwait(120.0; pollint=5.0) do + try + sock = connect(ip"127.0.0.1", 10000) + close(sock) + true + catch + false + end + end + + if listening !== :ok + @warn("test server did not start, stopping server") + kill(serverproc) + error("test server did not start") + end + + serverproc +end + +function test_generate() + @testset "codegen" begin + dir = joinpath(@__DIR__, "GrpcerrorsClients") + gRPCClient.generate(joinpath(@__DIR__, "error_test_server", "grpcerrors", "grpcerrors.proto"); outdir=dir) + @test isfile(joinpath(dir, "GrpcerrorsClients.jl")) + @test isfile(joinpath(dir, "grpcerrors.jl")) + @test isfile(joinpath(dir, "grpcerrors_pb.jl")) + end +end + +# switch off host verification for tests +if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) + ENV["JULIA_NO_VERIFY_HOSTS"] = "**" +end + +server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] +@info("server endpoint: $server_endpoint") + +@testset "Server Errors" begin + if !Sys.iswindows() + test_generate() + else + @info("skipping code generation on Windows to avoid needing batch file execution permissions") + end + include("test_grpcerrors.jl") + serverproc = start_server() + + @info("testing grpcerrors...") + test_clients(server_endpoint) + + kill(serverproc) + @info("stopped test server") +end + +end # module ErrorTest diff --git a/test/runtests_routeguide.jl b/test/runtests_routeguide.jl new file mode 100644 index 0000000..b6ce88d --- /dev/null +++ b/test/runtests_routeguide.jl @@ -0,0 +1,95 @@ +module RouteClientTest + +using gRPCClient +using Downloads +using Random +using Sockets +using Test + +const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.2/" +function server_binary() + arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" + filename = Sys.islinux() ? "routeguide_linux_$(arch)" : + Sys.iswindows() ? "routeguide_windows_$(arch).exe" : + Sys.isapple() ? "routeguide_darwin_$(arch)" : + error("no server binary available for this platform") + source = string(SERVER_RELEASE, filename) + destination = joinpath(@__DIR__, filename) + isfile(destination) || Downloads.download(source, destination) + ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) + + destination +end + +function start_server() + serverbin = server_binary() + + @info("starting test server", serverbin) + serverproc = run(`$serverbin`; wait=false) + + listening = timedwait(120.0; pollint=5.0) do + try + sock = connect(ip"127.0.0.1", 10000) + close(sock) + true + catch + false + end + end + + if listening !== :ok + @warn("test server did not start, stopping server") + kill(serverproc) + error("test server did not start") + end + + serverproc +end + +function test_generate() + @testset "codegen" begin + dir = joinpath(@__DIR__, "RouteguideClients") + gRPCClient.generate(joinpath(dir, "route_guide.proto"); outdir=dir) + @test isfile(joinpath(dir, "route_guide_pb.jl")) + @test isfile(joinpath(dir, "routeguide.jl")) + @test isfile(joinpath(dir, "RouteguideClients.jl")) + end +end + +function test_timeout_header_values() + @testset "timeout header" begin + @test "100S" == gRPCClient.grpc_timeout_header_val(100) + @test "100010m" == gRPCClient.grpc_timeout_header_val(100.01) + @test "100000100u" == gRPCClient.grpc_timeout_header_val(100.0001) + @test "100000010000n" == gRPCClient.grpc_timeout_header_val(100.00001) + end +end + +# switch off host verification for tests +if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) + ENV["JULIA_NO_VERIFY_HOSTS"] = "**" +end + +server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] +@info("server endpoint: $server_endpoint") + +@testset "RouteGuide" begin + if !Sys.iswindows() + test_generate() + else + @info("skipping code generation on Windows to avoid needing batch file execution permissions") + end + + test_timeout_header_values() + + include("test_routeclient.jl") + serverproc = start_server() + + @debug("testing routeclinet...") + test_clients(server_endpoint) + + kill(serverproc) + @info("stopped test server") +end + +end # module RouteClientTest diff --git a/test/test_grpcerrors.jl b/test/test_grpcerrors.jl new file mode 100644 index 0000000..f93aacb --- /dev/null +++ b/test/test_grpcerrors.jl @@ -0,0 +1,148 @@ +include("GrpcerrorsClients/GrpcerrorsClients.jl") +using .GrpcerrorsClients + +# GrpcerrorsClients.Data mode values: +# 1: throw an error after seconds provided in `param` +# 2: no error, just wait until seconds provided in `param`, respond with SimulationParams +Base.show(io::IO, data::GrpcerrorsClients.Data) = print(io, string("[", data.mode, ", ", data.param, "]")) + +# single request, single response +function test_simplerpc(client::GRPCErrorsBlockingClient) + data = GrpcerrorsClients.Data(; mode=1, param=0) + try + _, status_future = GrpcerrorsClients.SimpleRPC(client, data) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == "simulated error mode 1" + @test ex.grpc_status == StatusCode.UNKNOWN.code + end + + data = GrpcerrorsClients.Data(; mode=2, param=5) + try + _, status_future = GrpcerrorsClients.SimpleRPC(client, data) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == StatusCode.DEADLINE_EXCEEDED.message + @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code + end +end + +# single request, stream response +function test_stream_response(client::GRPCErrorsBlockingClient) + data = GrpcerrorsClients.Data(; mode=1, param=0) + try + _, status_future = GrpcerrorsClients.StreamResponse(client, data) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == "simulated error mode 1" + @test ex.grpc_status == StatusCode.UNKNOWN.code + end + + data = GrpcerrorsClients.Data(; mode=2, param=25) + try + _, status_future = GrpcerrorsClients.StreamResponse(client, data) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == StatusCode.DEADLINE_EXCEEDED.message + @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code + end +end + +# stream request, single response +function test_stream_request(client::GRPCErrorsBlockingClient) + try + data = GrpcerrorsClients.Data(; mode=1, param=0) + in = Channel{GrpcerrorsClients.Data}(1) + @async begin + put!(in, data) + close(in) + end + _, status_future = GrpcerrorsClients.StreamRequest(client, in) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == "simulated error mode 1" + @test ex.grpc_status == StatusCode.UNKNOWN.code + end + + try + data = GrpcerrorsClients.Data(; mode=2, param=5) + in = Channel{GrpcerrorsClients.Data}(1) + @async begin + put!(in, data) + close(in) + end + _, status_future = GrpcerrorsClients.StreamRequest(client, in) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == StatusCode.DEADLINE_EXCEEDED.message + @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code + end +end + +# stream request, stream response +function test_stream_request_response(client::GRPCErrorsBlockingClient) + try + data = GrpcerrorsClients.Data(; mode=1, param=0) + in = Channel{GrpcerrorsClients.Data}(1) + @async begin + put!(in, data) + close(in) + end + _, status_future = GrpcerrorsClients.StreamRequestResponse(client, in) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == "simulated error mode 1" + @test ex.grpc_status == StatusCode.UNKNOWN.code + end + + try + data = GrpcerrorsClients.Data(; mode=2, param=5) + in = Channel{GrpcerrorsClients.Data}(1) + @async begin + put!(in, data) + close(in) + end + _, status_future = GrpcerrorsClients.StreamRequestResponse(client, in) + gRPCCheck(status_future) + error("error not caught") + catch ex + @test isa(ex, gRPCServiceCallException) + @test ex.message == StatusCode.DEADLINE_EXCEEDED.message + @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code + end +end + +function test_blocking_client(server_endpoint::String) + client = GRPCErrorsBlockingClient(server_endpoint; verbose=false, request_timeout=3) + @testset "request response" begin + test_simplerpc(client) + end + @testset "streaming recv" begin + test_stream_response(client) + end + @testset "streaming send" begin + test_stream_request(client) + end + @testset "streaming send recv" begin + test_stream_request_response(client) + end +end + +function test_clients(server_endpoint::String) + @info("testing blocking client") + test_blocking_client(server_endpoint) +end \ No newline at end of file diff --git a/test/test_routeclient.jl b/test/test_routeclient.jl index bc294f6..847e35d 100644 --- a/test/test_routeclient.jl +++ b/test/test_routeclient.jl @@ -103,33 +103,27 @@ end function test_exception() client = RouteGuideBlockingClient("https://localhost:30000"; verbose=false) point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) - feature, status_future = RouteguideClients.GetFeature(client, point) - @test_throws gRPCServiceCallException gRPCCheck(status_future) - @test !gRPCCheck(status_future; throw_error=false) + @test_throws gRPCServiceCallException RouteguideClients.GetFeature(client, point) @test_throws ArgumentError RouteGuideBlockingClient("https://localhost:30000"; maxage=-1) end function test_message_length_limit(server_endpoint) + point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) + # test send message length limit client = RouteGuideBlockingClient(server_endpoint; max_message_length=1, verbose=false) - point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) - feature, status_future = RouteguideClients.GetFeature(client, point) - @test_throws gRPCMessageTooLargeException gRPCCheck(status_future) - @test !gRPCCheck(status_future; throw_error=false) + @test_throws gRPCMessageTooLargeException RouteguideClients.GetFeature(client, point) # test recv message length limit client = RouteGuideBlockingClient(server_endpoint; max_recv_message_length=1, verbose=false) - point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) - feature, status_future = RouteguideClients.GetFeature(client, point) - @test_throws gRPCMessageTooLargeException gRPCCheck(status_future) - @test !gRPCCheck(status_future; throw_error=false) + @test_throws gRPCMessageTooLargeException RouteguideClients.GetFeature(client, point) iob = IOBuffer() show(iob, gRPCMessageTooLargeException(1, 2)) @test String(take!(iob)) == "gRPMessageTooLargeException(1, 2) - Encountered message size 2 > max configured 1" - show(iob, gRPCServiceCallException("test error")) - @test String(take!(iob)) == "gRPCServiceCallException - test error" + show(iob, gRPCServiceCallException(0, "test error")) + @test String(take!(iob)) == "gRPCServiceCallException: 0, test error" end function test_async_get_feature(client::RouteGuideClient)