From c5c2fd740beb4ec88719a8d146a07b171bdcaed4 Mon Sep 17 00:00:00 2001 From: lysu Date: Wed, 15 May 2019 22:21:41 +0800 Subject: [PATCH 1/2] plugin: improve test cover --- plugin/const_test.go | 42 +++++++ plugin/helper_test.go | 54 ++++++++ plugin/plugin.go | 85 +++++++------ plugin/plugin_test.go | 278 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 415 insertions(+), 44 deletions(-) create mode 100644 plugin/const_test.go create mode 100644 plugin/helper_test.go create mode 100644 plugin/plugin_test.go diff --git a/plugin/const_test.go b/plugin/const_test.go new file mode 100644 index 0000000000000..dd366b41d2c4e --- /dev/null +++ b/plugin/const_test.go @@ -0,0 +1,42 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + "fmt" + "testing" +) + +func TestConstToString(t *testing.T) { + kinds := map[fmt.Stringer]string{ + Audit: "Audit", + Authentication: "Authentication", + Schema: "Schema", + Daemon: "Daemon", + Uninitialized: "Uninitialized", + Ready: "Ready", + Dying: "Dying", + Disable: "Disable", + Connected: "Connected", + Disconnect: "Disconnect", + ChangeUser: "ChangeUser", + PreAuth: "PreAuth", + ConnectionEvent(byte(15)): "", + } + for key, value := range kinds { + if key.String() != value { + t.Errorf("kind %s != %s", key.String(), kinds) + } + } +} diff --git a/plugin/helper_test.go b/plugin/helper_test.go new file mode 100644 index 0000000000000..1bb3fc71420ec --- /dev/null +++ b/plugin/helper_test.go @@ -0,0 +1,54 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import "testing" + +func TestPluginDeclare(t *testing.T) { + auditRaw := &AuditManifest{Manifest: Manifest{}} + auditExport := ExportManifest(auditRaw) + audit2 := DeclareAuditManifest(auditExport) + if audit2 != auditRaw { + t.Errorf("declare audit fail") + } + + authRaw := &AuthenticationManifest{Manifest: Manifest{}} + authExport := ExportManifest(authRaw) + auth2 := DeclareAuthenticationManifest(authExport) + if auth2 != authRaw { + t.Errorf("declare auth fail") + } + + schemaRaw := &SchemaManifest{Manifest: Manifest{}} + schemaExport := ExportManifest(schemaRaw) + schema2 := DeclareSchemaManifest(schemaExport) + if schema2 != schemaRaw { + t.Errorf("declare schema fail") + } + + daemonRaw := &DaemonManifest{Manifest: Manifest{}} + daemonExport := ExportManifest(daemonRaw) + daemon2 := DeclareDaemonManifest(daemonExport) + if daemon2 != daemonRaw { + t.Errorf("declare daemon fail") + } +} + +func TestDecode(t *testing.T) { + failID := ID("fail") + _, _, err := failID.Decode() + if err == nil { + t.Errorf("'fail' should not decode success") + } +} diff --git a/plugin/plugin.go b/plugin/plugin.go index ee5a7d68c6de6..23d82897d1bf8 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -49,8 +49,9 @@ type plugins struct { // clone deep copies plugins info. func (p *plugins) clone() *plugins { np := &plugins{ - plugins: make(map[Kind][]Plugin, len(p.plugins)), - versions: make(map[string]uint16, len(p.versions)), + plugins: make(map[Kind][]Plugin, len(p.plugins)), + versions: make(map[string]uint16, len(p.versions)), + dyingPlugins: make([]Plugin, len(p.dyingPlugins)), } for key, value := range p.plugins { np.plugins[key] = append([]Plugin(nil), value...) @@ -99,34 +100,7 @@ type Plugin struct { Path string } -type validateMode int - -const ( - initMode validateMode = iota - reloadMode -) - -func (p *Plugin) validate(ctx context.Context, tiPlugins *plugins, mode validateMode) error { - if mode == reloadMode { - var oldPlugin *Plugin - for i, item := range tiPlugins.plugins[p.Kind] { - if item.Name == p.Name { - oldPlugin = &tiPlugins.plugins[p.Kind][i] - break - } - } - if oldPlugin == nil { - return errUnsupportedReloadPlugin.GenWithStackByArgs(p.Name) - } - if len(p.SysVars) != len(oldPlugin.SysVars) { - return errUnsupportedReloadPluginVar.GenWithStackByArgs("") - } - for varName, varVal := range p.SysVars { - if oldPlugin.SysVars[varName] == nil || *oldPlugin.SysVars[varName] != *varVal { - return errUnsupportedReloadPluginVar.GenWithStackByArgs(varVal) - } - } - } +func (p *Plugin) validate(ctx context.Context, tiPlugins *plugins) error { if p.RequireVersion != nil { for component, reqVer := range p.RequireVersion { if ver, ok := tiPlugins.versions[component]; !ok || ver < reqVer { @@ -197,7 +171,7 @@ func Load(ctx context.Context, cfg Config) (err error) { // Cross validate & Load plugins. for kind := range tiPlugins.plugins { for i := range tiPlugins.plugins[kind] { - if err = tiPlugins.plugins[kind][i].validate(ctx, tiPlugins, initMode); err != nil { + if err = tiPlugins.plugins[kind][i].validate(ctx, tiPlugins); err != nil { if cfg.SkipWhenFail { logutil.Logger(ctx).Warn("validate plugin fail and disable plugin", zap.String("plugin", tiPlugins.plugins[kind][i].Name), zap.Error(err)) @@ -284,26 +258,25 @@ func (w *flushWatcher) watchLoop() { } } +type loadFn func(plugin *Plugin, dir string, pluginID ID) (manifest func() *Manifest, err error) + +var testHook *struct { + loadOne loadFn +} + func loadOne(dir string, pluginID ID) (plugin Plugin, err error) { - plugin.Path = filepath.Join(dir, string(pluginID)+LibrarySuffix) - plugin.library, err = gplugin.Open(plugin.Path) - if err != nil { - err = errors.Trace(err) - return - } - manifestSym, err := plugin.library.Lookup(ManifestSymbol) + pName, pVersion, err := pluginID.Decode() if err != nil { err = errors.Trace(err) return } - manifest, ok := manifestSym.(func() *Manifest) - if !ok { - err = errInvalidPluginManifest.GenWithStackByArgs(string(pluginID)) - return + var manifest func() *Manifest + if testHook == nil { + manifest, err = loadManifestByGoPlugin(&plugin, dir, pluginID) + } else { + manifest, err = testHook.loadOne(&plugin, dir, pluginID) } - pName, pVersion, err := pluginID.Decode() if err != nil { - err = errors.Trace(err) return } plugin.Manifest = manifest() @@ -318,6 +291,27 @@ func loadOne(dir string, pluginID ID) (plugin Plugin, err error) { return } +func loadManifestByGoPlugin(plugin *Plugin, dir string, pluginID ID) (manifest func() *Manifest, err error) { + plugin.Path = filepath.Join(dir, string(pluginID)+LibrarySuffix) + plugin.library, err = gplugin.Open(plugin.Path) + if err != nil { + err = errors.Trace(err) + return + } + manifestSym, err := plugin.library.Lookup(ManifestSymbol) + if err != nil { + err = errors.Trace(err) + return + } + var ok bool + manifest, ok = manifestSym.(func() *Manifest) + if !ok { + err = errInvalidPluginManifest.GenWithStackByArgs(string(pluginID)) + return + } + return +} + // Shutdown cleanups all plugin resources. // Notice: it just cleanups the resource of plugin, but cannot unload plugins(limited by go plugin). func Shutdown(ctx context.Context) { @@ -332,6 +326,9 @@ func Shutdown(ctx context.Context) { if p.flushWatcher != nil { p.flushWatcher.cancel() } + if p.OnShutdown == nil { + continue + } if err := p.OnShutdown(ctx, p.Manifest); err != nil { logutil.Logger(ctx).Error("call OnShutdown for failure", zap.String("plugin", p.Name), zap.Error(err)) diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go new file mode 100644 index 0000000000000..3026cdb52e77a --- /dev/null +++ b/plugin/plugin_test.go @@ -0,0 +1,278 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + "context" + "io" + "strconv" + "testing" + + "github.com/pingcap/tidb/sessionctx/variable" +) + +func TestLoadPluginSuccess(t *testing.T) { + ctx := context.Background() + + pluginName := "tplugin" + pluginVersion := uint16(1) + pluginSign := pluginName + "-" + strconv.Itoa(int(pluginVersion)) + + cfg := Config{ + Plugins: []string{pluginSign}, + PluginDir: "", + GlobalSysVar: &variable.SysVars, + PluginVarNames: &variable.PluginVarNames, + EnvVersion: map[string]uint16{"go": 1112}, + } + + // setup load test hook. + testHook = &struct{ loadOne loadFn }{loadOne: func(plugin *Plugin, dir string, pluginID ID) (manifest func() *Manifest, err error) { + return func() *Manifest { + m := &AuditManifest{ + Manifest: Manifest{ + Kind: Authentication, + Name: pluginName, + Version: pluginVersion, + SysVars: map[string]*variable.SysVar{pluginName + "_key": {Scope: variable.ScopeGlobal, Name: pluginName + "_key", Value: "v1"}}, + OnInit: func(ctx context.Context, manifest *Manifest) error { + return nil + }, + OnShutdown: func(ctx context.Context, manifest *Manifest) error { + return nil + }, + Validate: func(ctx context.Context, manifest *Manifest) error { + return nil + }, + }, + OnGeneralEvent: func(ctx context.Context, sctx *variable.SessionVars, event GeneralEvent, cmd string) { + }, + } + return ExportManifest(m) + }, nil + }} + defer func() { + testHook = nil + }() + + // trigger load. + err := Load(ctx, cfg) + if err != nil { + t.Errorf("load plugin [%s] fail", pluginSign) + } + + err = Init(ctx, cfg) + if err != nil { + t.Errorf("init plugin [%s] fail", pluginSign) + } + + // load all. + ps := GetAll() + if len(ps) != 1 { + t.Errorf("loaded plugins is empty") + } + + // find plugin by type and name + p := Get(Authentication, "tplugin") + if p == nil { + t.Errorf("tplugin can not be load") + } + p = Get(Authentication, "tplugin2") + if p != nil { + t.Errorf("found miss plugin") + } + p = getByName("tplugin") + if p == nil { + t.Errorf("can not find miss plugin") + } + + // foreach plugin + err = ForeachPlugin(Authentication, func(plugin *Plugin) error { + return nil + }) + if err != nil { + t.Errorf("foreach error %v", err) + } + err = ForeachPlugin(Authentication, func(plugin *Plugin) error { + return io.EOF + }) + if err != io.EOF { + t.Errorf("foreach should return EOF error") + } + + Shutdown(ctx) +} + +func TestLoadPluginSkipError(t *testing.T) { + ctx := context.Background() + + pluginName := "tplugin" + pluginVersion := uint16(1) + pluginSign := pluginName + "-" + strconv.Itoa(int(pluginVersion)) + + cfg := Config{ + Plugins: []string{pluginSign, pluginSign, "notExists-2"}, + PluginDir: "", + PluginVarNames: &variable.PluginVarNames, + EnvVersion: map[string]uint16{"go": 1112}, + SkipWhenFail: true, + } + + // setup load test hook. + testHook = &struct{ loadOne loadFn }{loadOne: func(plugin *Plugin, dir string, pluginID ID) (manifest func() *Manifest, err error) { + return func() *Manifest { + m := &AuditManifest{ + Manifest: Manifest{ + Kind: Audit, + Name: pluginName, + Version: pluginVersion, + SysVars: map[string]*variable.SysVar{pluginName + "_key": {Scope: variable.ScopeGlobal, Name: pluginName + "_key", Value: "v1"}}, + OnInit: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + OnShutdown: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + Validate: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + }, + OnGeneralEvent: func(ctx context.Context, sctx *variable.SessionVars, event GeneralEvent, cmd string) { + }, + } + return ExportManifest(m) + }, nil + }} + defer func() { + testHook = nil + }() + + // trigger load. + err := Load(ctx, cfg) + if err != nil { + t.Errorf("load plugin [%s] fail %v", pluginSign, err) + } + + err = Init(ctx, cfg) + if err != nil { + t.Errorf("init plugin [%s] fail", pluginSign) + } + + // load all. + ps := GetAll() + if len(ps) != 1 { + t.Errorf("loaded plugins is empty") + } + + // find plugin by type and name + p := Get(Audit, "tplugin") + if p == nil { + t.Errorf("tplugin can not be load") + } + p = Get(Audit, "tplugin2") + if p != nil { + t.Errorf("found miss plugin") + } + p = getByName("tplugin") + if p == nil { + t.Errorf("can not find miss plugin") + } + p = getByName("not exists") + if p != nil { + t.Errorf("got not exists plugin") + } + + // foreach plugin + readyCount := 0 + err = ForeachPlugin(Authentication, func(plugin *Plugin) error { + readyCount++ + return nil + }) + if readyCount != 0 { + t.Errorf("validate fail can be load but no ready") + } + + Shutdown(ctx) +} + +func TestLoadFail(t *testing.T) { + ctx := context.Background() + + pluginName := "tplugin" + pluginVersion := uint16(1) + pluginSign := pluginName + "-" + strconv.Itoa(int(pluginVersion)) + + cfg := Config{ + Plugins: []string{pluginSign, pluginSign, "notExists-2"}, + PluginDir: "", + PluginVarNames: &variable.PluginVarNames, + EnvVersion: map[string]uint16{"go": 1112}, + SkipWhenFail: false, + } + + // setup load test hook. + testHook = &struct{ loadOne loadFn }{loadOne: func(plugin *Plugin, dir string, pluginID ID) (manifest func() *Manifest, err error) { + return func() *Manifest { + m := &AuditManifest{ + Manifest: Manifest{ + Kind: Audit, + Name: pluginName, + Version: pluginVersion, + SysVars: map[string]*variable.SysVar{pluginName + "_key": {Scope: variable.ScopeGlobal, Name: pluginName + "_key", Value: "v1"}}, + OnInit: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + OnShutdown: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + Validate: func(ctx context.Context, manifest *Manifest) error { + return io.EOF + }, + }, + OnGeneralEvent: func(ctx context.Context, sctx *variable.SessionVars, event GeneralEvent, cmd string) { + }, + } + return ExportManifest(m) + }, nil + }} + defer func() { + testHook = nil + }() + + err := Load(ctx, cfg) + if err == nil { + t.Errorf("load plugin should fail") + } +} + +func TestPluginsClone(t *testing.T) { + ps := &plugins{ + plugins: map[Kind][]Plugin{ + Audit: {{}}, + }, + versions: map[string]uint16{ + "whitelist": 1, + }, + dyingPlugins: []Plugin{{}}, + } + cps := ps.clone() + ps.dyingPlugins = append(ps.dyingPlugins, Plugin{}) + ps.versions["w"] = 2 + as := ps.plugins[Audit] + ps.plugins[Audit] = append(as, Plugin{}) + + if len(cps.plugins) != 1 || len(cps.plugins[Audit]) != 1 || len(cps.versions) != 1 || len(cps.dyingPlugins) != 1 { + t.Errorf("clone plugins failure") + } +} From 628de6e26d0151bad7435d9844d829f05409c089 Mon Sep 17 00:00:00 2001 From: lysu Date: Thu, 16 May 2019 10:51:24 +0800 Subject: [PATCH 2/2] fix lint --- plugin/plugin_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 3026cdb52e77a..0f5acb6b26616 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -199,6 +199,9 @@ func TestLoadPluginSkipError(t *testing.T) { readyCount++ return nil }) + if err != nil { + t.Errorf("foreach meet error %v", err) + } if readyCount != 0 { t.Errorf("validate fail can be load but no ready") }