該程式創建了一個 rpc 服務器和一個客戶端,并通過 rpc 介面公開了幾個方法。
幾個測驗函式分別測驗其中一種方法。
第一個測驗函式注冊 rpc 服務器:
RPCServer := new(RPCServer)
rpc.Register(RPCServer)
rpc.HandleHTTP()
使用通道,每個函式等待服務器發出它正在運行的信號,當客戶端完成時,發出關閉服務器的信號。
在測驗功能中:
// start the server
ch := make(chan bool, 1)
go RunRPCServer(ch)
// wait for the server to start
<-ch
...Do Some Work...
// close the server
ch <- true
close(ch)
在服務器功能中:
listener, err := net.Listen("tcp4", "")
if err != nil {
log.Fatal("Could not open a listener port: ", err.Error())
}
wg := sync.WaitGroup{}
wg.Add(1)
// Start the server
rpcServer := http.Server{}
go func() {
go rpcServer.Serve(listener)
// signal that the server is running
ch <- true
// wait for the command to close the server
<-ch
<-ch
// shutdown server and exit
if err := rpcServer.Shutdown(context.Background()); err != nil {
log.Printf("HTTP server Shutdown: %v\n", err)
}
listener.Close()
wg.Done()
}()
wg.Wait()
運行測驗一次,甚至多次運行測驗時,一切都運行良好:
去測驗 -count 1 ./src/rpcserver/
去測驗 -count 1 ./src/rpcserver/
但是,運行時
go test -count 2 ./src/rpcserver/
第二次運行時回傳錯誤:
--- FAIL: TestRPCServer (0.00s)
panic: http: multiple registrations for / goRPC [recovered]
panic: http: multiple registrations for / goRPC
我想知道為什么
go test -count 2 ./src/rpcserver/
不完全像:
去測驗 -count 1 ./src/rpcserver/
去測驗 -count 1 ./src/rpcserver/
?
為了在連續運行測驗之間取消注冊服務器,可以做些什么?
編輯: 這是一個說明問題的完整最小示例:
服務器.go:
package rpcserver
import (
"context"
"log"
"net"
"net/http"
"net/rpc"
)
type RPCServer int
// RPC: only methods that comply with the following scheme are exported:
// func (t *T) MethodName(argType T1, replyType *T2) error
// Since RPC-exposed methods must takes an argument.
type EmptyArg struct{}
// Expose several methods via the RPC interface:
func (RpcServer *RPCServer) Method_1(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_1"
return nil
}
func (RpcServer *RPCServer) Method_2(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_2"
return nil
}
func (RpcServer *RPCServer) Method_3(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_3"
return nil
}
// should be called only once per process
func RegisterRPCMethods() {
// Register the rpc methods
RpcServer := new(RPCServer)
rpc.Register(RpcServer)
rpc.HandleHTTP()
}
func RunRPCServer(ch chan struct{}) {
// Open a listener port
listener, err := net.Listen("tcp4", "127.0.0.1:38659")
if err != nil {
log.Fatal("listen error:", err)
}
// Print some data
log.Println("Server running")
log.Println("network:", listener.Addr().Network())
// Start the server
rpcServer := http.Server{}
go func() {
go rpcServer.Serve(listener)
// signal that the server is running
ch <- struct{}{}
// wait for the command to close the server
<-ch
// shutdown server and exit
if err := rpcServer.Shutdown(context.Background()); err != nil {
log.Printf("HTTP server Shutdown: %v\n", err)
}
listener.Close()
// signal that the server is closed
ch <- struct{}{}
}()
}
server_test.go:
package rpcserver
import (
"net/rpc"
"testing"
)
func TestMethod_1(t *testing.T) {
// call once.
// (test functions are executed in-order)
RegisterRPCMethods()
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_1", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
func TestMethod_2(t *testing.T) {
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_2", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
func TestMethod_3(t *testing.T) {
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_3", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
運行時:
去測驗 -v -count 1 ./src/server/...
輸出如預期:
=== RUN TestMethod_1
2022/05/11 10:59:49 服務器運行
2022/05/11 10:59:49 網路:tcp
--- PASS:TestMethod_1 (0.00s)
=== RUN TestMethod_2
2022/05/11 10:59:49 服務器運行
2022/05/11 10:59:49 網路:tcp
--- PASS:TestMethod_2 (0.00s)
=== RUN TestMethod_3
2022/05/11 10:59:49 服務器運行
2022/05 /11 10:59:49 網路:tcp
--- PASS:TestMethod_3 (0.00s)
PASS
ok lsfrpc/src/server 0.008s
而且跑步的時候
去測驗 -v -count 1 ./src/server/...
去測驗 -v -count 1 ./src/server/...
一切正常(上面的輸出,兩次)
但是,運行時:
去測驗 -v -count 2 ./src/server/...
第二次運行開始時出現錯誤:
=== RUN TestMethod_1
2022/05/11 10:59:52 服務器運行
2022/05/11 10:59:52 網路:tcp
--- PASS:TestMethod_1 (0.00s)
=== RUN TestMethod_2
2022/05/11 10:59:52 服務器運行
2022/05/11 10:59:52 網路:tcp
--- PASS:TestMethod_2 (0.00s)
=== RUN TestMethod_3
2022/05/11 10:59:52 服務器運行
2022/05 /11 10:59:52 network: tcp
--- PASS: TestMethod_3 (0.00s)
=== RUN TestMethod_1
--- FAIL: TestMethod_1 (0.00s)
panic: http: multiple registrations for / goRPC [recovered]
panic: http :/ goRPC
goroutine 63 [running]的多個注冊:
testing.tRunner.func1.2({0x7b9f20, 0xc0002927f0})
/home/sivsha01/go/src/testing/testing.go:1389 0x24e
testing.tRunner.func1()
/home/sivsha01/go/src/testing/ testing.go:1392 0x39f
panic({0x7b9f20, 0xc0002927f0})
/home/sivsha01/go/src/runtime/panic.go:838 0x207
net/http.(*ServeMux).Handle(0xb2e160, {0x83449c, 0x8 },{0x8dc3c0?,0xc000198000})
和我上面描述的一樣。這對我來說似乎是一種錯誤的行為。
uj5u.com熱心網友回復:
重現該問題的簡化版本:
package rpcserver
import (
"net/http"
"os"
"testing"
)
func TestRegister(t *testing.T) {
t.Logf("pid: %d", os.Getpid())
register()
}
func register() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
}
當您使用: 運行測驗時go test -count 2 -v,您會發現 2 個測驗在同一行程中運行。正如您在評論中所述:RegisterRPCMethods() 每個行程只應呼叫一次。
您可以使用sync.Once來確保它只被呼叫一次:
package rpcserver
import (
"net/http"
"os"
"sync"
"testing"
)
func TestRegister(t *testing.T) {
t.Logf("pid: %d", os.Getpid())
register()
}
var registerOnce sync.Once
func register() {
registerOnce.Do(func() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
})
}
更重要的是,您的測驗將不再依賴于測驗的執行順序。并且所有測驗都可以單獨運行。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/473046.html
上一篇:處理恐慌后繼續執行函式
下一篇:對匯入包進行單元測驗
