1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-21 09:36:34 -05:00

testing with mock

This commit is contained in:
Darien Raymond 2018-10-22 15:58:52 +02:00
parent 307aac26b3
commit f78cf6cfc2
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
8 changed files with 251 additions and 122 deletions

View File

@ -89,22 +89,25 @@ type DefaultDispatcher struct {
stats stats.Manager stats stats.Manager
} }
// NewDefaultDispatcher create a new DefaultDispatcher. func init() {
func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := &DefaultDispatcher{} d := new(DefaultDispatcher)
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
core.RequireFeatures(ctx, d.Init) return d.Init(config.(*Config), om, router, pm, sm)
}); err != nil {
return d, nil return nil, err
}
return d, nil
}))
} }
// Init initializes DefaultDispatcher. // Init initializes DefaultDispatcher.
// This method is visible for testing purpose. func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
func (d *DefaultDispatcher) Init(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) {
d.ohm = om d.ohm = om
d.router = router d.router = router
d.policy = pm d.policy = pm
d.stats = sm d.stats = sm
return nil
} }
// Type implements common.HasType. // Type implements common.HasType.
@ -257,9 +260,3 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *vio.Link,
} }
dispatcher.Dispatch(ctx, link) dispatcher.Dispatch(ctx, link)
} }
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewDefaultDispatcher(ctx, config.(*Config))
}))
}

View File

@ -14,6 +14,18 @@ import (
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
) )
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
r := new(Router)
if err := core.RequireFeatures(ctx, func(d dns.Client) error {
return r.Init(config.(*Config), d)
}); err != nil {
return nil, err
}
return r, nil
}))
}
// Router is an implementation of routing.Router. // Router is an implementation of routing.Router.
type Router struct { type Router struct {
domainStrategy Config_DomainStrategy domainStrategy Config_DomainStrategy
@ -21,27 +33,22 @@ type Router struct {
dns dns.Client dns dns.Client
} }
// NewRouter creates a new Router based on the given config. // Init initializes the Router.
func NewRouter(ctx context.Context, config *Config) (*Router, error) { func (r *Router) Init(config *Config, d dns.Client) error {
r := &Router{ r.domainStrategy = config.DomainStrategy
domainStrategy: config.DomainStrategy, r.rules = make([]Rule, len(config.Rule))
rules: make([]Rule, len(config.Rule)), r.dns = d
}
for idx, rule := range config.Rule { for idx, rule := range config.Rule {
r.rules[idx].Tag = rule.Tag r.rules[idx].Tag = rule.Tag
cond, err := rule.BuildCondition() cond, err := rule.BuildCondition()
if err != nil { if err != nil {
return nil, err return err
} }
r.rules[idx].Condition = cond r.rules[idx].Condition = cond
} }
core.RequireFeatures(ctx, func(d dns.Client) { return nil
r.dns = d
})
return r, nil
} }
type ipResolver struct { type ipResolver struct {
@ -127,9 +134,3 @@ func (*Router) Close() error {
func (*Router) Type() interface{} { func (*Router) Type() interface{} {
return routing.RouterType() return routing.RouterType()
} }
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewRouter(ctx, config.(*Config))
}))
}

View File

@ -3,46 +3,71 @@ package router_test
import ( import (
"testing" "testing"
"v2ray.com/core" "github.com/golang/mock/gomock"
"v2ray.com/core/app/dispatcher"
"v2ray.com/core/app/proxyman"
_ "v2ray.com/core/app/proxyman/outbound"
. "v2ray.com/core/app/router" . "v2ray.com/core/app/router"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/serial"
"v2ray.com/core/common/session" "v2ray.com/core/common/session"
"v2ray.com/core/features/routing" "v2ray.com/core/features/mocks"
. "v2ray.com/ext/assert"
) )
func TestSimpleRouter(t *testing.T) { func TestSimpleRouter(t *testing.T) {
assert := With(t) config := &Config{
Rule: []*RoutingRule{
config := &core.Config{ {
App: []*serial.TypedMessage{ Tag: "test",
serial.ToTypedMessage(&Config{ NetworkList: &net.NetworkList{
Rule: []*RoutingRule{ Network: []net.Network{net.Network_TCP},
{
Tag: "test",
NetworkList: &net.NetworkList{
Network: []net.Network{net.Network_TCP},
},
},
}, },
}), },
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
}, },
} }
v, err := core.New(config) mockCtl := gomock.NewController(t)
common.Must(err) defer mockCtl.Finish()
r := v.GetFeature(routing.RouterType()).(routing.Router) mockDns := mocks.NewMockDNSClient(mockCtl)
r := new(Router)
common.Must(r.Init(config, mockDns))
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(ctx)
assert(err, IsNil) common.Must(err)
assert(tag, Equals, "test") if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag)
}
}
func TestIPOnDemand(t *testing.T) {
config := &Config{
DomainStrategy: Config_IpOnDemand,
Rule: []*RoutingRule{
{
Tag: "test",
Cidr: []*CIDR{
{
Ip: []byte{192, 168, 0, 0},
Prefix: 16,
},
},
},
},
}
mockCtl := gomock.NewController(t)
defer mockCtl.Finish()
mockDns := mocks.NewMockDNSClient(mockCtl)
mockDns.EXPECT().LookupIP(gomock.Eq("v2ray.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
r := new(Router)
common.Must(r.Init(config, mockDns))
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx)
common.Must(err)
if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag)
}
} }

83
features/mocks/dns.go Normal file
View File

@ -0,0 +1,83 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: v2ray.com/core/features/dns (interfaces: Client)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
net "net"
reflect "reflect"
)
// MockDNSClient is a mock of Client interface
type MockDNSClient struct {
ctrl *gomock.Controller
recorder *MockDNSClientMockRecorder
}
// MockDNSClientMockRecorder is the mock recorder for MockDNSClient
type MockDNSClientMockRecorder struct {
mock *MockDNSClient
}
// NewMockDNSClient creates a new mock instance
func NewMockDNSClient(ctrl *gomock.Controller) *MockDNSClient {
mock := &MockDNSClient{ctrl: ctrl}
mock.recorder = &MockDNSClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDNSClient) EXPECT() *MockDNSClientMockRecorder {
return m.recorder
}
// Close mocks base method
func (m *MockDNSClient) Close() error {
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close
func (mr *MockDNSClientMockRecorder) Close() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDNSClient)(nil).Close))
}
// LookupIP mocks base method
func (m *MockDNSClient) LookupIP(arg0 string) ([]net.IP, error) {
ret := m.ctrl.Call(m, "LookupIP", arg0)
ret0, _ := ret[0].([]net.IP)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LookupIP indicates an expected call of LookupIP
func (mr *MockDNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*MockDNSClient)(nil).LookupIP), arg0)
}
// Start mocks base method
func (m *MockDNSClient) Start() error {
ret := m.ctrl.Call(m, "Start")
ret0, _ := ret[0].(error)
return ret0
}
// Start indicates an expected call of Start
func (mr *MockDNSClientMockRecorder) Start() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockDNSClient)(nil).Start))
}
// Type mocks base method
func (m *MockDNSClient) Type() interface{} {
ret := m.ctrl.Call(m, "Type")
ret0, _ := ret[0].(interface{})
return ret0
}
// Type indicates an expected call of Type
func (mr *MockDNSClientMockRecorder) Type() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockDNSClient)(nil).Type))
}

5
mocks.go Normal file
View File

@ -0,0 +1,5 @@
package core
//go:generate go get -u github.com/golang/mock/gomock
//go:generate go install github.com/golang/mock/mockgen
//go:generate mockgen -package mocks -destination v2ray.com/core/features/mocks/dns.go v2ray.com/core/features/dns Client

View File

@ -19,6 +19,16 @@ import (
"v2ray.com/core/transport/pipe" "v2ray.com/core/transport/pipe"
) )
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := new(DokodemoDoor)
err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
return d.Init(config.(*Config), pm)
})
return d, err
}))
}
type DokodemoDoor struct { type DokodemoDoor struct {
policyManager policy.Manager policyManager policy.Manager
config *Config config *Config
@ -26,19 +36,17 @@ type DokodemoDoor struct {
port net.Port port net.Port
} }
func New(ctx context.Context, config *Config) (*DokodemoDoor, error) { // Init initializes the DokodemoDoor instance with necessary parameters.
func (d *DokodemoDoor) Init(config *Config, pm policy.Manager) error {
if config.NetworkList == nil || config.NetworkList.Size() == 0 { if config.NetworkList == nil || config.NetworkList.Size() == 0 {
return nil, newError("no network specified") return newError("no network specified")
}
v := core.MustFromContext(ctx)
d := &DokodemoDoor{
config: config,
address: config.GetPredefinedAddress(),
port: net.Port(config.Port),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
} }
d.config = config
d.address = config.GetPredefinedAddress()
d.port = net.Port(config.Port)
d.policyManager = pm
return d, nil return nil
} }
func (d *DokodemoDoor) Network() net.NetworkList { func (d *DokodemoDoor) Network() net.NetworkList {
@ -144,9 +152,3 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
return nil return nil
} }
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
}

View File

@ -22,6 +22,18 @@ import (
"v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet"
) )
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler)
if err := core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) error {
return h.Init(config.(*Config), pm, d)
}); err != nil {
return nil, err
}
return h, nil
}))
}
// Handler handles Freedom connections. // Handler handles Freedom connections.
type Handler struct { type Handler struct {
policyManager policy.Manager policyManager policy.Manager
@ -29,18 +41,13 @@ type Handler struct {
config Config config Config
} }
// New creates a new Freedom handler. // Init initializes the Handler with necessary parameters.
func New(ctx context.Context, config *Config) (*Handler, error) { func (h *Handler) Init(config *Config, pm policy.Manager, d dns.Client) error {
f := &Handler{ h.config = *config
config: *config, h.policyManager = pm
} h.dns = d
core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) { return nil
f.policyManager = pm
f.dns = d
})
return f, nil
} }
func (h *Handler) policy() policy.Session { func (h *Handler) policy() policy.Session {
@ -163,9 +170,3 @@ func (h *Handler) Process(ctx context.Context, link *vio.Link, dialer proxy.Dial
return nil return nil
} }
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
}

View File

@ -40,12 +40,12 @@ func getFeature(allFeatures []features.Feature, t reflect.Type) features.Feature
return nil return nil
} }
func (r *resolution) resolve(allFeatures []features.Feature) bool { func (r *resolution) resolve(allFeatures []features.Feature) (bool, error) {
var fs []features.Feature var fs []features.Feature
for _, d := range r.deps { for _, d := range r.deps {
f := getFeature(allFeatures, d) f := getFeature(allFeatures, d)
if f == nil { if f == nil {
return false return false, nil
} }
fs = append(fs, f) fs = append(fs, f)
} }
@ -67,9 +67,16 @@ func (r *resolution) resolve(allFeatures []features.Feature) bool {
panic("Can't get all input parameters") panic("Can't get all input parameters")
} }
callback.Call(input) var err error
ret := callback.Call(input)
errInterface := reflect.TypeOf((*error)(nil)).Elem()
for i := len(ret) - 1; i >= 0; i-- {
if ret[i].Type().Implements(errInterface) {
err = ret[i].Interface().(error)
}
}
return true return true, err
} }
// Instance combines all functionalities in V2Ray. // Instance combines all functionalities in V2Ray.
@ -134,9 +141,9 @@ func addOutboundHandlers(server *Instance, configs []*OutboundHandlerConfig) err
// RequireFeatures is a helper function to require features from Instance in context. // RequireFeatures is a helper function to require features from Instance in context.
// See Instance.RequireFeatures for more information. // See Instance.RequireFeatures for more information.
func RequireFeatures(ctx context.Context, callback interface{}) { func RequireFeatures(ctx context.Context, callback interface{}) error {
v := MustFromContext(ctx) v := MustFromContext(ctx)
v.RequireFeatures(callback) return v.RequireFeatures(callback)
} }
// New returns a new V2Ray instance based on given configuration. // New returns a new V2Ray instance based on given configuration.
@ -162,29 +169,30 @@ func New(config *Config) (*Instance, error) {
return nil, err return nil, err
} }
if feature, ok := obj.(features.Feature); ok { if feature, ok := obj.(features.Feature); ok {
server.AddFeature(feature) if err := server.AddFeature(feature); err != nil {
return nil, err
}
} }
} }
if server.GetFeature(dns.ClientType()) == nil { essentialFeatures := []struct {
server.AddFeature(dns.LocalClient{}) Type interface{}
Instance features.Feature
}{
{dns.ClientType(), dns.LocalClient{}},
{policy.ManagerType(), policy.DefaultManager{}},
{routing.RouterType(), routing.DefaultRouter{}},
{stats.ManagerType(), stats.NoopManager{}},
} }
if server.GetFeature(policy.ManagerType()) == nil { for _, f := range essentialFeatures {
server.AddFeature(policy.DefaultManager{}) if server.GetFeature(f.Type) == nil {
if err := server.AddFeature(f.Instance); err != nil {
return nil, err
}
}
} }
if server.GetFeature(routing.RouterType()) == nil {
server.AddFeature(routing.DefaultRouter{})
}
if server.GetFeature(stats.ManagerType()) == nil {
server.AddFeature(stats.NoopManager{})
}
// Add an empty instance at the end, for optional feature requirement.
server.AddFeature(&Instance{})
if server.featureResolutions != nil { if server.featureResolutions != nil {
return nil, newError("not all dependency are resolved.") return nil, newError("not all dependency are resolved.")
} }
@ -227,7 +235,7 @@ func (s *Instance) Close() error {
// RequireFeatures registers a callback, which will be called when all dependent features are registered. // RequireFeatures registers a callback, which will be called when all dependent features are registered.
// The callback must be a func(). All its parameters must be features.Feature. // The callback must be a func(). All its parameters must be features.Feature.
func (s *Instance) RequireFeatures(callback interface{}) { func (s *Instance) RequireFeatures(callback interface{}) error {
callbackType := reflect.TypeOf(callback) callbackType := reflect.TypeOf(callback)
if callbackType.Kind() != reflect.Func { if callbackType.Kind() != reflect.Func {
panic("not a function") panic("not a function")
@ -242,30 +250,35 @@ func (s *Instance) RequireFeatures(callback interface{}) {
deps: featureTypes, deps: featureTypes,
callback: callback, callback: callback,
} }
if r.resolve(s.features) { if finished, err := r.resolve(s.features); finished {
return return err
} }
s.featureResolutions = append(s.featureResolutions, r) s.featureResolutions = append(s.featureResolutions, r)
return nil
} }
// AddFeature registers a feature into current Instance. // AddFeature registers a feature into current Instance.
func (s *Instance) AddFeature(feature features.Feature) { func (s *Instance) AddFeature(feature features.Feature) error {
s.features = append(s.features, feature) s.features = append(s.features, feature)
if s.running { if s.running {
if err := feature.Start(); err != nil { if err := feature.Start(); err != nil {
newError("failed to start feature").Base(err).WriteToLog() newError("failed to start feature").Base(err).WriteToLog()
} }
return return nil
} }
if s.featureResolutions == nil { if s.featureResolutions == nil {
return return nil
} }
var pendingResolutions []resolution var pendingResolutions []resolution
for _, r := range s.featureResolutions { for _, r := range s.featureResolutions {
if !r.resolve(s.features) { finished, err := r.resolve(s.features)
if finished && err != nil {
return err
}
if !finished {
pendingResolutions = append(pendingResolutions, r) pendingResolutions = append(pendingResolutions, r)
} }
} }
@ -274,6 +287,8 @@ func (s *Instance) AddFeature(feature features.Feature) {
} else if len(pendingResolutions) < len(s.featureResolutions) { } else if len(pendingResolutions) < len(s.featureResolutions) {
s.featureResolutions = pendingResolutions s.featureResolutions = pendingResolutions
} }
return nil
} }
// GetFeature returns a feature of the given type, or nil if such feature is not registered. // GetFeature returns a feature of the given type, or nil if such feature is not registered.