diff --git a/.github/workflows/code.yaml b/.github/workflows/code.yaml index 69c5ada1..eebf5bb4 100644 --- a/.github/workflows/code.yaml +++ b/.github/workflows/code.yaml @@ -11,7 +11,7 @@ on: - ".github/workflows/code.yml" env: - GO_VERSION: 1.18 + GO_VERSION: 1.19 jobs: @@ -34,7 +34,7 @@ jobs: - name: Spelling Check uses: reviewdog/action-misspell@v1.12.2 - name: Revive Action - uses: morphy2k/revive-action@v2.3.1 + uses: morphy2k/revive-action@v2 - name: "Restore Permissions" run: | sudo chown -R $(id -u) $GITHUB_WORKSPACE diff --git a/channel/channel.go b/channel/channel.go index 48740037..fe257621 100644 --- a/channel/channel.go +++ b/channel/channel.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package channel implements the channel interface package channel import ( @@ -159,7 +160,7 @@ func (c *Channel) WatchEvent(wg *sync.WaitGroup) { log.Infof("[%s / %s]: %s (%s) - Status changed [%s] ==> [%s]", kind, c.Name, result.Name, result.Endpoint, result.PreStatus, result.Status) for _, n := range c.Notifiers { - if dryNotify { + if IsDryNotify() == true { n.DryNotify(result) } else { go n.Notify(result) diff --git a/channel/manager.go b/channel/manager.go index d4f51dba..ebc27aea 100644 --- a/channel/manager.go +++ b/channel/manager.go @@ -19,6 +19,7 @@ package channel import ( "sync" + "sync/atomic" "github.com/megaease/easeprobe/notify" "github.com/megaease/easeprobe/probe" @@ -26,11 +27,20 @@ import ( var channel = make(map[string]*Channel) var wg sync.WaitGroup -var dryNotify bool +var dryNotify atomic.Value + +func init() { + SetDryNotify(false) +} // SetDryNotify sets the global dry run flag func SetDryNotify(dry bool) { - dryNotify = dry + dryNotify.Store(dry) +} + +// IsDryNotify returns the dry run flag +func IsDryNotify() bool { + return dryNotify.Load().(bool) } // GetAllChannels returns all channels diff --git a/channel/manager_test.go b/channel/manager_test.go index b78aeeaf..682da6f0 100644 --- a/channel/manager_test.go +++ b/channel/manager_test.go @@ -186,5 +186,16 @@ func TestManager(t *testing.T) { time.Sleep(200 * time.Millisecond) assert.Equal(t, nGoroutine, runtime.NumGoroutine()) + // test the dry notification + SetDryNotify(true) + assert.True(t, IsDryNotify()) + for _, ch := range chs { + for _, p := range ch.Probers { + res := p.Probe() + assert.NotNil(t, res) + ch.Send(res) + } + } + AllDone() } diff --git a/cmd/easeprobe/main.go b/cmd/easeprobe/main.go index c6f38304..a63c7294 100644 --- a/cmd/easeprobe/main.go +++ b/cmd/easeprobe/main.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package main is the entry point for the easeprobe command. package main import ( @@ -107,14 +108,11 @@ func main() { // if dry notification mode is specified in command line, overwrite the configuration if *dryNotify { c.Settings.Notify.Dry = *dryNotify + log.Infoln("Dry Notification Mode...") } // set the dry notify flag to channel channel.SetDryNotify(c.Settings.Notify.Dry) - if c.Settings.Notify.Dry { - log.Infoln("Dry Notification Mode...") - } - //////////////////////////////////////////////////////////////////////////// // Start the HTTP Server // //////////////////////////////////////////////////////////////////////////// diff --git a/conf/conf.go b/conf/conf.go index bf8154ed..514afb35 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -15,9 +15,11 @@ * limitations under the License. */ +// Package conf is the configuration of the application package conf import ( + "fmt" "io/ioutil" httpClient "net/http" netUrl "net/url" @@ -56,30 +58,40 @@ type Schedule int // Schedule enum const ( - Hourly Schedule = iota + None Schedule = iota + Hourly Daily Weekly Monthly - None ) +var scheduleToString = map[Schedule]string{ + Hourly: "hourly", + Daily: "daily", + Weekly: "weekly", + Monthly: "monthly", + None: "none", +} + +var stringToSchedule = global.ReverseMap(scheduleToString) + +// MarshalYAML marshal the configuration to yaml +func (s Schedule) MarshalYAML() (interface{}, error) { + if s, ok := scheduleToString[s]; ok { + return s, nil + } + return nil, fmt.Errorf("unknown schedule %d", s) +} + // UnmarshalYAML is unmarshal the debug level func (s *Schedule) UnmarshalYAML(unmarshal func(interface{}) error) error { var level string if err := unmarshal(&level); err != nil { return err } - switch strings.ToLower(level) { - case "hourly": - *s = Hourly - case "daily": - *s = Daily - case "weekly": - *s = Weekly - case "monthly": - *s = Monthly - default: - *s = None + var ok bool + if *s, ok = stringToSchedule[strings.ToLower(level)]; !ok { + return fmt.Errorf("unknown schedule %s", level) } return nil } diff --git a/conf/conf_test.go b/conf/conf_test.go index 52aafed8..9ea406f7 100644 --- a/conf/conf_test.go +++ b/conf/conf_test.go @@ -18,9 +18,12 @@ package conf import ( + "errors" "fmt" + "io" "io/ioutil" "net/http" + httpClient "net/http" "os" "testing" "time" @@ -41,6 +44,7 @@ import ( "github.com/megaease/easeprobe/probe/tcp" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func testisExternalURL(url string, expects bool, t *testing.T) { @@ -64,6 +68,34 @@ func TestPathAndURL(t *testing.T) { testisExternalURL("ftp://127.0.0.1", false, t) } +func testScheduleYaml(t *testing.T, name string, sch Schedule, good bool) { + var s Schedule + err := yaml.Unmarshal([]byte(name), &s) + if good { + assert.Nil(t, err) + assert.Equal(t, sch, s) + } else { + assert.NotNil(t, err) + } + + buf, err := yaml.Marshal(sch) + if good { + assert.Nil(t, err) + assert.Equal(t, name+"\n", string(buf)) + } else { + assert.NotNil(t, err) + } +} +func TestScheduleYaml(t *testing.T) { + testScheduleYaml(t, "hourly", Hourly, true) + testScheduleYaml(t, "daily", Daily, true) + testScheduleYaml(t, "weekly", Weekly, true) + testScheduleYaml(t, "monthly", Monthly, true) + testScheduleYaml(t, "none", None, true) + testScheduleYaml(t, "yearly", 100, false) + testScheduleYaml(t, "- bad", 100, false) +} + func TestGetYamlFileFromFile(t *testing.T) { if _, err := getYamlFileFromFile("/tmp/nonexistent"); err == nil { t.Errorf("getYamlFileFromFile(\"/tmp/nonexistent\") = nil, expected error") @@ -107,6 +139,8 @@ http: interval: 30s channels: - "telegram#Dev" + - name: Env Variables + url: $WEB_SITE ` func checkHTTPProbe(t *testing.T, probe httpProbe.HTTP) { @@ -124,6 +158,8 @@ func checkHTTPProbe(t *testing.T, probe httpProbe.HTTP) { assert.Equal(t, probe.ProbeTimeout, 2*time.Minute) assert.Equal(t, probe.ProbeTimeInterval, 30*time.Second) assert.Equal(t, probe.Channels(), []string{"telegram#Dev"}) + case "Env Variables": + assert.Equal(t, probe.URL, os.Getenv("WEB_SITE")) default: t.Errorf("unexpected probe name %s", probe.ProbeName) } @@ -557,8 +593,15 @@ func TestConfig(t *testing.T) { err := writeConfig(file, confYAML) assert.Nil(t, err) - conf, err := New(&file) + // bad config + os.Setenv("WEB_SITE", "\n - x::") + _, err = New(&file) + assert.NotNil(t, err) + + os.Setenv("WEB_SITE", "https://easeprobe.com") + _, err = New(&file) assert.Nil(t, err) + conf := Get() assert.Equal(t, "EaseProbeBot", conf.Settings.Name) assert.Equal(t, "0.1.0", conf.Version) @@ -583,7 +626,7 @@ func TestConfig(t *testing.T) { conf.InitAllLogs() probers := conf.AllProbers() - assert.Equal(t, 8, len(probers)) + assert.Equal(t, 9, len(probers)) notifiers := conf.AllNotifiers() assert.Equal(t, 6, len(notifiers)) @@ -597,12 +640,30 @@ func TestConfig(t *testing.T) { assert.Equal(t, "0.1.0", httpConf.Version) probers = conf.AllProbers() - assert.Equal(t, 8, len(probers)) + assert.Equal(t, 9, len(probers)) notifiers = conf.AllNotifiers() assert.Equal(t, 6, len(notifiers)) os.RemoveAll(file) os.RemoveAll("data") + + // error test + url = "http://localhost:65534" + _, err = New(&url) + assert.NotNil(t, err) + + os.Setenv("HTTP_TIMEOUT", "invalid") + _, err = New(&url) + assert.NotNil(t, err) + + monkey.Patch(httpClient.NewRequest, func(method, url string, body io.Reader) (*http.Request, error) { + return nil, errors.New("error") + }) + url = "http://localhost" + _, err = New(&url) + assert.NotNil(t, err) + + monkey.UnpatchAll() } func TestInitData(t *testing.T) { diff --git a/conf/log.go b/conf/log.go index df32a0c9..d9edf096 100644 --- a/conf/log.go +++ b/conf/log.go @@ -18,6 +18,7 @@ package conf import ( + "fmt" "io" "os" "strings" @@ -40,18 +41,14 @@ var levelToString = map[LogLevel]string{ LogLevel(log.PanicLevel): "panic", } -var stringToLevel = map[string]LogLevel{ - "debug": LogLevel(log.DebugLevel), - "info": LogLevel(log.InfoLevel), - "warn": LogLevel(log.WarnLevel), - "error": LogLevel(log.ErrorLevel), - "fatal": LogLevel(log.FatalLevel), - "panic": LogLevel(log.PanicLevel), -} +var stringToLevel = global.ReverseMap(levelToString) // MarshalYAML is marshal the format func (l LogLevel) MarshalYAML() (interface{}, error) { - return levelToString[l], nil + if s, ok := levelToString[l]; ok { + return s, nil + } + return nil, fmt.Errorf("invalid log level: %d", l) } // UnmarshalYAML is unmarshal the debug level @@ -60,7 +57,10 @@ func (l *LogLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&level); err != nil { return err } - *l = stringToLevel[strings.ToLower(level)] + var ok bool + if *l, ok = stringToLevel[strings.ToLower(level)]; !ok { + return fmt.Errorf("invalid log level: %s", level) + } return nil } @@ -197,7 +197,6 @@ func (l *Log) Rotate() { l.Open() // open another writer l.ConfigureLogger() // set the new logger writer. } - } // ConfigureLogger configure the logger diff --git a/conf/log_test.go b/conf/log_test.go index 52c3aa5b..a4eb89f4 100644 --- a/conf/log_test.go +++ b/conf/log_test.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "reflect" "testing" "bou.ke/monkey" @@ -30,17 +31,55 @@ import ( "gopkg.in/yaml.v3" ) +func testLogYaml(t *testing.T, name string, level LogLevel, good bool) { + var l LogLevel + err := yaml.Unmarshal([]byte(name), &l) + if good { + assert.Nil(t, err) + assert.Equal(t, level, l) + } else { + assert.NotNil(t, err) + assert.Equal(t, LogLevel(log.PanicLevel), l) + } + + buf, err := yaml.Marshal(level) + if good { + assert.Nil(t, err) + assert.Equal(t, name+"\n", string(buf)) + } else { + assert.NotNil(t, err) + assert.Nil(t, buf) + } + +} + +func TestLogLevelYaml(t *testing.T) { + testLogYaml(t, "debug", LogLevel(log.DebugLevel), true) + testLogYaml(t, "info", LogLevel(log.InfoLevel), true) + testLogYaml(t, "warn", LogLevel(log.WarnLevel), true) + testLogYaml(t, "error", LogLevel(log.ErrorLevel), true) + testLogYaml(t, "fatal", LogLevel(log.FatalLevel), true) + testLogYaml(t, "panic", LogLevel(log.PanicLevel), true) + testLogYaml(t, "none", LogLevel(log.Level(100)), false) + testLogYaml(t, "- none", LogLevel(log.Level(100)), false) +} + func TestLogLevel(t *testing.T) { l := LogLevel(log.DebugLevel) buf, err := yaml.Marshal(l) assert.Nil(t, err) assert.Equal(t, "debug\n", string(buf)) - err = yaml.Unmarshal([]byte("painc"), &l) + err = yaml.Unmarshal([]byte("panic"), &l) assert.Nil(t, err) assert.Equal(t, LogLevel(log.PanicLevel), l) assert.Equal(t, l.GetLevel(), log.PanicLevel) + + // test error yaml format + err = yaml.Unmarshal([]byte("- log::error"), &l) + assert.NotNil(t, err) + assert.Equal(t, log.PanicLevel, l.GetLevel()) } func testLogs(t *testing.T, name string, new bool, app bool, selfRotate bool) { @@ -101,7 +140,6 @@ func TestOpenLogFail(t *testing.T) { monkey.Patch(os.OpenFile, func(name string, flag int, perm os.FileMode) (*os.File, error) { return nil, fmt.Errorf("error") }) - defer monkey.UnpatchAll() l := NewLog() l.File = "test.log" @@ -109,4 +147,52 @@ func TestOpenLogFail(t *testing.T) { l.InitLog(nil) assert.Equal(t, true, l.IsStdout) assert.Equal(t, os.Stdout, l.GetWriter()) + + l.Close() + l.Writer = nil + l.Rotate() + + w := l.GetWriter() + assert.Equal(t, os.Stdout, w) + + monkey.UnpatchAll() + + // test rotate error - log file + l = NewLog() + l.File = "test.log" + l.SelfRotate = false + l.InitLog(nil) + assert.Equal(t, false, l.IsStdout) + + var fp *os.File + monkey.PatchInstanceMethod(reflect.TypeOf(fp), "Close", func(_ *os.File) error { + return fmt.Errorf("error") + }) + + l.Rotate() + files, _ := filepath.Glob("test*") + fmt.Println(files) + assert.Equal(t, 1, len(files)) + l.Close() + os.Remove(l.File) + + // test rotate error - lumberjackLogger + l = NewLog() + l.File = "test.log" + l.SelfRotate = true + l.InitLog(nil) + assert.Equal(t, false, l.IsStdout) + + var lum *lumberjack.Logger + monkey.PatchInstanceMethod(reflect.TypeOf(lum), "Rotate", func(_ *lumberjack.Logger) error { + return fmt.Errorf("error") + }) + l.Rotate() + files, _ = filepath.Glob("test*") + fmt.Println(files) + assert.Equal(t, 1, len(files)) + l.Close() + os.Remove(l.File) + + monkey.UnpatchAll() } diff --git a/daemon/daemon.go b/daemon/daemon.go index b7b29490..33197a57 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package daemon is the daemon implementation. package daemon import ( diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 57ca029e..ff719db5 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -142,3 +142,35 @@ func TestPIDFileFailed(t *testing.T) { monkey.UnpatchAll() } + +func TestCheckPIDFile(t *testing.T) { + file := "dir/easedprobe.pid" + conf, err := NewPIDFile(file) + assert.Nil(t, err) + assert.FileExists(t, file) + + pid, err := conf.CheckPIDFile() + assert.Equal(t, os.Getpid(), pid) + assert.NotNil(t, err) + + monkey.Patch(processExists, func(int) bool { + return false + }) + pid, err = conf.CheckPIDFile() + assert.Equal(t, -1, pid) + assert.Nil(t, err) + + os.WriteFile(conf.PIDFile, []byte("invalid pid"), 0644) + pid, err = conf.CheckPIDFile() + assert.Equal(t, -1, pid) + assert.Nil(t, err) + + conf.RemovePIDFile() + os.RemoveAll("dir") + + pid, err = conf.CheckPIDFile() + assert.Equal(t, -1, pid) + assert.Nil(t, err) + + monkey.UnpatchAll() +} diff --git a/eval/eval.go b/eval/eval.go index e4b45289..fc2149a3 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package eval is the package to eval the expression and extract the value from the document package eval import ( diff --git a/global/easeprobe.go b/global/easeprobe.go index bb723d62..e1d9dd7e 100644 --- a/global/easeprobe.go +++ b/global/easeprobe.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package global is the package for EaseProbe package global import ( diff --git a/metric/prometheus.go b/metric/prometheus.go index fa527652..57d4c49b 100644 --- a/metric/prometheus.go +++ b/metric/prometheus.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package metric is the package to report the metrics to Prometheus package metric import ( diff --git a/notify/aws/conf.go b/notify/aws/conf.go index 2a99feec..c9391029 100644 --- a/notify/aws/conf.go +++ b/notify/aws/conf.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package aws is the AWS notification package package aws import ( diff --git a/notify/base/base.go b/notify/base/base.go index 3cf7e6e3..75115cde 100644 --- a/notify/base/base.go +++ b/notify/base/base.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package base is the base implementation of the notification. package base import ( diff --git a/notify/dingtalk/dingtalk.go b/notify/dingtalk/dingtalk.go index a095cb46..9656c5b0 100644 --- a/notify/dingtalk/dingtalk.go +++ b/notify/dingtalk/dingtalk.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package dingtalk is the notification package for dingtalk. package dingtalk import ( diff --git a/notify/discord/discord.go b/notify/discord/discord.go index 31964cae..88792e7a 100644 --- a/notify/discord/discord.go +++ b/notify/discord/discord.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package discord is the notification for Discord package discord import ( diff --git a/notify/email/email.go b/notify/email/email.go index 0ebcee76..0e548319 100644 --- a/notify/email/email.go +++ b/notify/email/email.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package email is the email notification package package email import ( diff --git a/notify/lark/lark.go b/notify/lark/lark.go index b01afa53..5af0cd71 100644 --- a/notify/lark/lark.go +++ b/notify/lark/lark.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package lark is the lark notification package. package lark import ( diff --git a/notify/log/format.go b/notify/log/format.go index c657da68..66fbc5e1 100644 --- a/notify/log/format.go +++ b/notify/log/format.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package log is the log package for easeprobe. package log import ( diff --git a/notify/notify.go b/notify/notify.go index 5ad49de4..a8eb42ef 100644 --- a/notify/notify.go +++ b/notify/notify.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package notify contains the notify implementation. package notify import ( diff --git a/notify/shell/shell.go b/notify/shell/shell.go index 52416ea5..a27407d4 100644 --- a/notify/shell/shell.go +++ b/notify/shell/shell.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package shell is the shell probe package package shell import ( diff --git a/notify/slack/slack.go b/notify/slack/slack.go index 5913f0c6..275be5d8 100644 --- a/notify/slack/slack.go +++ b/notify/slack/slack.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package slack is the slack notification package. package slack import ( diff --git a/notify/sms/conf/conf.go b/notify/sms/conf/conf.go index 44b1b8c0..d5282619 100644 --- a/notify/sms/conf/conf.go +++ b/notify/sms/conf/conf.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package conf is the configuration package for SMS notification package conf import ( diff --git a/notify/sms/nexmo/nexmo.go b/notify/sms/nexmo/nexmo.go index a9293866..e6fa806e 100644 --- a/notify/sms/nexmo/nexmo.go +++ b/notify/sms/nexmo/nexmo.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package nexmo is the nexmo sms notification package package nexmo import ( diff --git a/notify/sms/sms.go b/notify/sms/sms.go index bb5ed394..9b0d11ad 100644 --- a/notify/sms/sms.go +++ b/notify/sms/sms.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package sms contains the sms implementation. package sms import ( diff --git a/notify/sms/twilio/twilio.go b/notify/sms/twilio/twilio.go index 3c49d940..f5bf0367 100644 --- a/notify/sms/twilio/twilio.go +++ b/notify/sms/twilio/twilio.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package twilio is the twilio sms notification package twilio import ( diff --git a/notify/sms/yunpian/yunpian.go b/notify/sms/yunpian/yunpian.go index 6f5d2cf4..81f87a7c 100644 --- a/notify/sms/yunpian/yunpian.go +++ b/notify/sms/yunpian/yunpian.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package yunpian is the yunpian sms notification. package yunpian import ( diff --git a/notify/teams/teams.go b/notify/teams/teams.go index a04784f2..d999b8ff 100644 --- a/notify/teams/teams.go +++ b/notify/teams/teams.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package teams is the teams notification package teams import ( diff --git a/notify/telegram/telegram.go b/notify/telegram/telegram.go index 004fd341..3d99ba55 100644 --- a/notify/telegram/telegram.go +++ b/notify/telegram/telegram.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package telegram is the telegram notification package. package telegram import ( diff --git a/notify/wecom/wecom.go b/notify/wecom/wecom.go index 5a2b9342..483cc106 100644 --- a/notify/wecom/wecom.go +++ b/notify/wecom/wecom.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package wecom is the wecom notification package. package wecom import ( diff --git a/probe/base/base.go b/probe/base/base.go index 8c75b84c..e794e7bb 100644 --- a/probe/base/base.go +++ b/probe/base/base.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package base is the base package for all probes package base import ( diff --git a/probe/client/client.go b/probe/client/client.go index 50720803..5f64418a 100644 --- a/probe/client/client.go +++ b/probe/client/client.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package client is the native client probe package package client import ( diff --git a/probe/client/conf/conf.go b/probe/client/conf/conf.go index 6c424acc..435deee5 100644 --- a/probe/client/conf/conf.go +++ b/probe/client/conf/conf.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package conf is the configuration package for native client package conf import ( @@ -119,7 +120,11 @@ func (d *DriverType) DriverType(name string) DriverType { // MarshalYAML is marshal the driver type func (d DriverType) MarshalYAML() (interface{}, error) { - return d.String(), nil + v := d.String() + if v == "unknown" { + return nil, fmt.Errorf("Unknown driver") + } + return v, nil } // UnmarshalYAML is unmarshal the driver type @@ -128,7 +133,9 @@ func (d *DriverType) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&s); err != nil { return err } - *d = d.DriverType(strings.ToLower(s)) + if *d = d.DriverType(strings.ToLower(s)); *d == Unknown { + return fmt.Errorf("Unknown driver: %s", s) + } return nil } @@ -138,11 +145,17 @@ func (d *DriverType) UnmarshalJSON(b []byte) (err error) { if err = json.Unmarshal(b, &s); err != nil { return err } - *d = d.DriverType(strings.ToLower(s)) + if *d = d.DriverType(strings.ToLower(s)); *d == Unknown { + return fmt.Errorf("Unknown driver: %s", s) + } return nil } // MarshalJSON is marshal the driver func (d DriverType) MarshalJSON() (b []byte, err error) { - return []byte(fmt.Sprintf(`"%s"`, d.String())), nil + v := d.String() + if v == "unknown" { + return nil, fmt.Errorf("Unknown driver") + } + return []byte(fmt.Sprintf(`"%s"`, v)), nil } diff --git a/probe/client/conf/conf_test.go b/probe/client/conf/conf_test.go index 372fc9c5..2ea23610 100644 --- a/probe/client/conf/conf_test.go +++ b/probe/client/conf/conf_test.go @@ -25,7 +25,43 @@ import ( "gopkg.in/yaml.v3" ) -func TestDirverType(t *testing.T) { +func testMarshalUnmarshal( + t *testing.T, str string, driver DriverType, good bool, + marshal func(in interface{}) ([]byte, error), + unmarshal func(in []byte, out interface{}) (err error)) { + + var d DriverType + err := unmarshal([]byte(str), &d) + if good { + assert.Nil(t, err) + assert.Equal(t, driver, d) + } else { + assert.Error(t, err) + assert.Equal(t, Unknown, d) + } + + buf, err := marshal(driver) + if good { + assert.Nil(t, err) + assert.Equal(t, str, string(buf)) + } else { + assert.Error(t, err) + assert.Nil(t, buf) + } +} + +func testYamlJSON(t *testing.T, str string, drive DriverType, good bool) { + testYaml(t, str+"\n", drive, good) + testJSON(t, `"`+str+`"`, drive, good) +} +func testYaml(t *testing.T, str string, drive DriverType, good bool) { + testMarshalUnmarshal(t, str, drive, good, yaml.Marshal, yaml.Unmarshal) +} +func testJSON(t *testing.T, str string, drive DriverType, good bool) { + testMarshalUnmarshal(t, str, drive, good, json.Marshal, json.Unmarshal) +} + +func TestDriverType(t *testing.T) { assert.Equal(t, "mysql", MySQL.String()) assert.Equal(t, "redis", Redis.String()) assert.Equal(t, "memcache", Memcache.String()) @@ -36,36 +72,23 @@ func TestDirverType(t *testing.T) { assert.Equal(t, Redis, d.DriverType("redis")) assert.Equal(t, Memcache, d.DriverType("memcache")) - d = d.DriverType("postgres") - buf, err := yaml.Marshal(d) - assert.Nil(t, err) - assert.Equal(t, "postgres\n", string(buf)) - - err = yaml.Unmarshal([]byte("zookeeper"), &d) - assert.Nil(t, err) - assert.Equal(t, Zookeeper, d) - - err = yaml.Unmarshal([]byte("xxx"), &d) - assert.Nil(t, err) - assert.Equal(t, Unknown, d) - - d = MySQL - buf, err = json.Marshal(d) - assert.Nil(t, err) - assert.Equal(t, "\"mysql\"", string(buf)) - - err = json.Unmarshal([]byte("\"mongo\""), &d) - assert.Nil(t, err) - assert.Equal(t, Mongo, d) - - d = Memcache - buf, err = json.Marshal(d) - assert.Nil(t, err) - assert.Equal(t, "\"memcache\"", string(buf)) - d = 10 assert.Equal(t, "unknown", d.String()) + testYamlJSON(t, "mysql", MySQL, true) + testYamlJSON(t, "redis", Redis, true) + testYamlJSON(t, "memcache", Memcache, true) + testYamlJSON(t, "kafka", Kafka, true) + testYamlJSON(t, "mongo", Mongo, true) + testYamlJSON(t, "postgres", PostgreSQL, true) + testYamlJSON(t, "zookeeper", Zookeeper, true) + testYamlJSON(t, "unknown", Unknown, false) + + testJSON(t, "", Unknown, false) + testJSON(t, `{"x":"y"}`, Unknown, false) + testJSON(t, `"xyz"`, Unknown, false) + testYaml(t, "- mysql::", Unknown, false) + } func TestOptionsCheck(t *testing.T) { diff --git a/probe/client/kafka/kafka.go b/probe/client/kafka/kafka.go index 642b2222..bc0a71c4 100644 --- a/probe/client/kafka/kafka.go +++ b/probe/client/kafka/kafka.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package kafka is the native client probe for kafka. package kafka import ( diff --git a/probe/client/memcache/memcache.go b/probe/client/memcache/memcache.go index 8cd5e6e5..85123751 100644 --- a/probe/client/memcache/memcache.go +++ b/probe/client/memcache/memcache.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package memcache is the native client probe for memcache package memcache import ( diff --git a/probe/client/mongo/mongo.go b/probe/client/mongo/mongo.go index 188f8a09..5c9567c0 100644 --- a/probe/client/mongo/mongo.go +++ b/probe/client/mongo/mongo.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package mongo implements a probe client for the MongoDB database. package mongo import ( diff --git a/probe/client/mysql/mysql.go b/probe/client/mysql/mysql.go index 3357ee58..963f1df8 100644 --- a/probe/client/mysql/mysql.go +++ b/probe/client/mysql/mysql.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package mysql is the client probe for MySQL. package mysql import ( diff --git a/probe/client/postgres/postgres.go b/probe/client/postgres/postgres.go index c1c52b1f..558cd2b1 100644 --- a/probe/client/postgres/postgres.go +++ b/probe/client/postgres/postgres.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package postgres is the native client probe for PostgreSQL package postgres import ( diff --git a/probe/client/redis/redis.go b/probe/client/redis/redis.go index f0c64920..2a0dd47e 100644 --- a/probe/client/redis/redis.go +++ b/probe/client/redis/redis.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package redis is the native client probe for Redis package redis import ( diff --git a/probe/client/zookeeper/zookeeper.go b/probe/client/zookeeper/zookeeper.go index 52ece3e4..c2f2ab38 100644 --- a/probe/client/zookeeper/zookeeper.go +++ b/probe/client/zookeeper/zookeeper.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package zookeeper is the zookeeper client probe package zookeeper import ( diff --git a/probe/data_test.go b/probe/data_test.go index 1bbed712..e4b5f729 100644 --- a/probe/data_test.go +++ b/probe/data_test.go @@ -22,9 +22,11 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "testing" "time" + "bou.ke/monkey" "github.com/megaease/easeprobe/global" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" @@ -79,13 +81,13 @@ var testResults = []Result{ } func newDataFile(file string) error { - makeAll(file) + makeAllDir(file) SetResultsData(testResults) return SaveDataToFile(file) } func newDataFileWithOutMeta(file string) error { - makeAll(file) + makeAllDir(file) SetResultsData(testResults) buf, err := yaml.Marshal(resultData) if err != nil { @@ -108,7 +110,7 @@ func removeDataFile(file string) { os.Remove(file) } -func makeAll(file string) { +func makeAllDir(file string) { dir, _ := filepath.Split(file) os.MkdirAll(dir, 0755) } @@ -142,10 +144,28 @@ func TestNewDataFile(t *testing.T) { //custom data file file = "x/y/z/mydata.yaml" - makeAll(file) + makeAllDir(file) newDataFile(file) assert.True(t, isDataFileExisted(file)) removeAll("x/") + + // errors + monkey.Patch(ioutil.WriteFile, func(filename string, data []byte, perm os.FileMode) error { + return fmt.Errorf("error") + }) + file = "data.yaml" + err := newDataFile(file) + assert.Error(t, err) + removeAll(file) + + monkey.Patch(yaml.Marshal, func(v interface{}) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + err = newDataFile(file) + assert.Error(t, err) + removeAll(file) + + monkey.UnpatchAll() } func TestLoadDataFile(t *testing.T) { @@ -163,6 +183,47 @@ func TestLoadDataFile(t *testing.T) { assert.True(t, isDataFileExisted(metaData.backup)) removeAll(metaData.backup) checkData(t) + + // errors - backup file + monkey.Patch(os.Rename, func(oldpath, newpath string) error { + return fmt.Errorf("error") + }) + newDataFile(file) + err = LoadDataFromFile(file) + assert.Nil(t, err) + assert.False(t, isDataFileExisted(metaData.backup)) + removeAll(file) + + // errors - unmarshal error + newDataFile(file) + monkey.Patch(yaml.Unmarshal, func(in []byte, out interface{}) error { + return fmt.Errorf("error") + }) + err = LoadDataFromFile(file) + assert.Error(t, err) + removeAll(file) + monkey.UnpatchAll() + + // errors - decode error + newDataFile(file) + var dec *yaml.Decoder + monkey.PatchInstanceMethod(reflect.TypeOf(dec), "Decode", func(*yaml.Decoder, interface{}) error { + return fmt.Errorf("error") + }) + err = LoadDataFromFile(file) + assert.Error(t, err) + removeAll(file) + + // errors - read error + newDataFile(file) + monkey.Patch(ioutil.ReadFile, func(filename string) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + err = LoadDataFromFile(file) + assert.Error(t, err) + removeAll(file) + + monkey.UnpatchAll() } func numOfBackup(file string) int { @@ -185,10 +246,22 @@ func TestCleanDataFile(t *testing.T) { t.Fatal(err) } files, _ := filepath.Glob(file + "-*") - fmt.Printf("n=%d, files=%v\n", n, files) + fmt.Printf("i=%d, files=%v\n", i, files) + time.Sleep(100 * time.Millisecond) } assert.Equal(t, n, numOfBackup(file)) + monkey.Patch(os.Remove, func(filename string) error { + return fmt.Errorf("error") + }) + CleanDataFile(file, 3) + assert.Equal(t, n, numOfBackup(file)) + + monkey.UnpatchAll() + + CleanDataFile(file, 10) + assert.Equal(t, n, numOfBackup(file)) + n = 3 CleanDataFile(file, n) assert.Equal(t, n, numOfBackup(file)) @@ -199,6 +272,23 @@ func TestCleanDataFile(t *testing.T) { // clean data file removeAll("data/") + + // disable data file + file = "-" + CleanDataFile(file, n) + assert.Equal(t, 0, numOfBackup(file)) + + file = "data.yaml" + CleanDataFile(file, -1) + assert.Equal(t, 0, numOfBackup(file)) + + monkey.Patch(filepath.Glob, func(pattern string) ([]string, error) { + return nil, fmt.Errorf("error") + }) + CleanDataFile(file, n) + assert.Equal(t, 0, numOfBackup(file)) + + monkey.UnpatchAll() } func TestMetaData(t *testing.T) { @@ -234,6 +324,17 @@ func TestMetaData(t *testing.T) { assert.Equal(t, m.ver, "v1.0.0") removeAll("data/") + + // case four: set empty meta + SetMetaData("", "") + newDataFile(file) + err := LoadDataFromFile(file) + assert.Nil(t, err) + m = GetMetaData() + assert.Equal(t, m.Name, global.DefaultProg) + assert.Equal(t, m.Ver, global.Ver) + removeAll("data/") + } type DummyProbe struct { @@ -286,7 +387,11 @@ func TestCleanData(t *testing.T) { MyName: testResults[1].Name, MyResult: &Result{Name: testResults[1].Name}, }, + &DummyProbe{ + MyName: "dummy3", + MyResult: &Result{Name: "dummy3"}, + }, } CleanData(p) - assert.Equal(t, len(resultData), 2) + assert.Equal(t, len(resultData), 3) } diff --git a/probe/host/host.go b/probe/host/host.go index b0a232c4..54b5631d 100644 --- a/probe/host/host.go +++ b/probe/host/host.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package host is the host probe package package host import ( diff --git a/probe/http/http.go b/probe/http/http.go index a73f4cab..95e2851d 100644 --- a/probe/http/http.go +++ b/probe/http/http.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package http is the HTTP probe package. package http import ( diff --git a/probe/http/http_test.go b/probe/http/http_test.go index 7f963f4f..d6e9bcea 100644 --- a/probe/http/http_test.go +++ b/probe/http/http_test.go @@ -99,6 +99,13 @@ func TestHTTPConfig(t *testing.T) { err = h.Config(global.ProbeSettings{}) assert.NoError(t, err) + var e *eval.Evaluator + monkey.PatchInstanceMethod(reflect.TypeOf(e), "Config", func(_ *eval.Evaluator) error { + return fmt.Errorf("Eval Config Error") + }) + err = h.Config(global.ProbeSettings{}) + assert.Error(t, err) + h.Proxy = "\nexample.com" err = h.Config(global.ProbeSettings{}) assert.Error(t, err) diff --git a/probe/probe.go b/probe/probe.go index 09bce3c8..5db1f4c8 100644 --- a/probe/probe.go +++ b/probe/probe.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package probe contains the probe implementation. package probe import ( diff --git a/probe/result_test.go b/probe/result_test.go index f3181d88..0a8e8d8d 100644 --- a/probe/result_test.go +++ b/probe/result_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "bou.ke/monkey" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) @@ -211,6 +212,20 @@ func TestDebug(t *testing.T) { if str != expected { t.Errorf("%s != %s", str, expected) } + + monkey.Patch(json.Marshal, func(v interface{}) ([]byte, error) { + return nil, fmt.Errorf("marshal error") + }) + monkey.Patch(json.MarshalIndent, func(v any, prefix string, indent string) ([]byte, error) { + return nil, fmt.Errorf("marshal error") + }) + + str = r.DebugJSON() + assert.Empty(t, str) + str = r.DebugJSONIndent() + assert.Empty(t, str) + + monkey.UnpatchAll() } func TestSLAPercent(t *testing.T) { diff --git a/probe/shell/shell.go b/probe/shell/shell.go index 38eaa5dc..ef824bff 100644 --- a/probe/shell/shell.go +++ b/probe/shell/shell.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package shell is the shell probe package package shell import ( diff --git a/probe/ssh/endpoint_test.go b/probe/ssh/endpoint_test.go index 4812b8d4..8448a731 100644 --- a/probe/ssh/endpoint_test.go +++ b/probe/ssh/endpoint_test.go @@ -111,4 +111,11 @@ DX4y9QvXoaUiQ5vB63voiqRTBT4hTYBDVi2G7NEjIczNs9S8JQM5Mg52mZsdH77g6ChUPp config, err = e.SSHConfig("ssh", "test", 30*time.Second) assert.Nil(t, err) assert.NotNil(t, config) + + monkey.Patch(ioutil.ReadFile, func(filename string) ([]byte, error) { + return []byte(``), nil + }) + config, err = e.SSHConfig("ssh", "test", 30*time.Second) + assert.Nil(t, config) + assert.NotNil(t, err) } diff --git a/probe/ssh/ssh.go b/probe/ssh/ssh.go index da061fb8..f55f32d0 100644 --- a/probe/ssh/ssh.go +++ b/probe/ssh/ssh.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package ssh is the ssh probe package package ssh import ( diff --git a/probe/ssh/ssh_test.go b/probe/ssh/ssh_test.go index 3ce726bd..3f399816 100644 --- a/probe/ssh/ssh_test.go +++ b/probe/ssh/ssh_test.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "net" "reflect" + "strings" "testing" "time" @@ -88,7 +89,7 @@ func createSSHConfig() *SSH { }, Command: "test", Args: []string{}, - Env: []string{}, + Env: []string{"TEST=test"}, BastionID: "none", }, }, @@ -224,6 +225,25 @@ YWwBAg== } } + checkServer := func(s bool, m string) { + for _, server := range _ssh.Servers { + status, message := server.DoProbe() + assert.Equal(t, s, status) + assert.Contains(t, message, m) + } + } + // session.Run failed + monkey.PatchInstanceMethod(reflect.TypeOf(ss), "Run", func(s *ssh.Session, cmd string) error { + return errors.New("session.Run failed") + }) + checkServer(false, "session.Run failed") + + // client.NewSession failed + monkey.PatchInstanceMethod(reflect.TypeOf(client), "NewSession", func(c *ssh.Client) (*ssh.Session, error) { + return nil, errors.New("client.NewSession failed") + }) + checkServer(false, "client.NewSession failed") + // NewClientConn failed monkey.Patch(ssh.NewClientConn, func(c net.Conn, addr string, config *ssh.ClientConfig) (ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { return &ssh.Client{}, nil, nil, errors.New("NewClientConn failed") @@ -248,25 +268,50 @@ YWwBAg== } } + // SSHConfig failed - no bastion + var guard *monkey.PatchGuard + var ed *Endpoint + guard = monkey.PatchInstanceMethod(reflect.TypeOf(ed), "SSHConfig", func(e *Endpoint, kind, name string, timeout time.Duration) (*ssh.ClientConfig, error) { + guard.Unpatch() + defer guard.Restore() + if strings.Contains(e.Host, "bastion") { + return e.SSHConfig(kind, name, timeout) + } + return nil, errors.New("SSHConfig failed") + }) + checkServer(false, "SSHConfig failed") + + // SSHConfig failed + monkey.PatchInstanceMethod(reflect.TypeOf(ed), "SSHConfig", func(e *Endpoint, kind, name string, timeout time.Duration) (*ssh.ClientConfig, error) { + return nil, errors.New("SSHConfig failed") + }) + checkServer(false, "SSHConfig failed") + // ssh Dial failed + monkey.UnpatchInstanceMethod(reflect.TypeOf(ed), "SSHConfig") monkey.Patch(ssh.Dial, func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { return nil, errors.New("ssh Dial failed") }) - for _, s := range _ssh.Servers { - status, message := s.DoProbe() - assert.Equal(t, false, status) - assert.Contains(t, message, "ssh Dial failed") - } + checkServer(false, "ssh Dial failed") - // SSHConfig failed - var ed *Endpoint - monkey.PatchInstanceMethod(reflect.TypeOf(ed), "SSHConfig", func(e *Endpoint, _, _ string, _ time.Duration) (*ssh.ClientConfig, error) { - return nil, errors.New("SSHConfig failed") + var s *Server + monkey.PatchInstanceMethod(reflect.TypeOf(s), "RunSSHCmd", func(s *Server) (string, error) { + if s.bastion != nil { + return "ExitMissingError", &ssh.ExitMissingError{} + } + return "ExitError", &ssh.ExitError{ + Waitmsg: ssh.Waitmsg{}, + } }) for _, s := range _ssh.Servers { status, message := s.DoProbe() - assert.Equal(t, false, status) - assert.Contains(t, message, "SSHConfig failed") + if s.bastion != nil { + assert.Equal(t, false, status) + assert.Contains(t, message, "ExitMissingError") + } else { + assert.Equal(t, false, status) + assert.Contains(t, message, "ExitError") + } } monkey.UnpatchAll() diff --git a/probe/status.go b/probe/status.go index f0aaf640..10095100 100644 --- a/probe/status.go +++ b/probe/status.go @@ -19,6 +19,7 @@ package probe import ( "encoding/json" + "fmt" "strings" "github.com/megaease/easeprobe/global" @@ -84,29 +85,43 @@ func (s *Status) Emoji() string { // UnmarshalYAML is Unmarshal the status func (s *Status) UnmarshalYAML(unmarshal func(interface{}) error) error { var status string + *s = StatusUnknown if err := unmarshal(&status); err != nil { return err } - s.Status(status) - return nil + if val, ok := toStatus[strings.ToLower(status)]; ok { + *s = val + return nil + } + return fmt.Errorf("Unknown status: %s", status) } // MarshalYAML is Marshal the status func (s Status) MarshalYAML() (interface{}, error) { - return s.String(), nil + if val, ok := toString[s]; ok { + return val, nil + } + return "unknown", fmt.Errorf("Unknown status: %s", s) } // UnmarshalJSON is Unmarshal the status func (s *Status) UnmarshalJSON(b []byte) (err error) { var str string + *s = StatusUnknown if err = json.Unmarshal(b, &str); err != nil { return err } - s.Status(str) - return nil + if val, ok := toStatus[strings.ToLower(str)]; ok { + *s = val + return nil + } + return fmt.Errorf("Unknown status: %s", str) } // MarshalJSON is marshal the status func (s Status) MarshalJSON() (b []byte, err error) { - return json.Marshal(s.String()) + if val, ok := toString[s]; ok { + return []byte(fmt.Sprintf(`"%s"`, val)), nil + } + return []byte("unknown"), fmt.Errorf("Unknown status: %s", s) } diff --git a/probe/status_test.go b/probe/status_test.go index 3bf78d71..452db654 100644 --- a/probe/status_test.go +++ b/probe/status_test.go @@ -25,6 +25,54 @@ import ( "gopkg.in/yaml.v3" ) +func testMarshalUnmarshal(t *testing.T, str string, status Status, good bool, + marshal func(in interface{}) ([]byte, error), + unmarshal func(in []byte, out interface{}) (err error)) { + + var s Status + err := unmarshal([]byte(str), &s) + if good { + assert.Nil(t, err) + assert.Equal(t, status, s) + } else { + assert.Error(t, err) + assert.Equal(t, StatusUnknown, s) + } + + buf, err := marshal(status) + if good { + assert.Nil(t, err) + assert.Equal(t, str, string(buf)) + } else { + assert.Error(t, err) + assert.Nil(t, buf) + } +} + +func testYamlJSON(t *testing.T, str string, status Status, good bool) { + testYaml(t, str+"\n", status, good) + testJSON(t, `"`+str+`"`, status, good) +} +func testYaml(t *testing.T, str string, status Status, good bool) { + testMarshalUnmarshal(t, str, status, good, yaml.Marshal, yaml.Unmarshal) +} +func testJSON(t *testing.T, str string, status Status, good bool) { + testMarshalUnmarshal(t, str, status, good, json.Marshal, json.Unmarshal) +} + +func TestYamlJSON(t *testing.T) { + testYamlJSON(t, "init", StatusInit, true) + testYamlJSON(t, "up", StatusUp, true) + testYamlJSON(t, "down", StatusDown, true) + testYamlJSON(t, "unknown", StatusUnknown, true) + testYamlJSON(t, "bad", StatusBad, true) + + testYamlJSON(t, "xxx", 10, false) + + testYaml(t, "- xxx", 10, false) + testJSON(t, `{"x":"y"}`, 10, false) +} + func TestStatus(t *testing.T) { s := StatusUp assert.Equal(t, "up", s.String()) @@ -34,6 +82,10 @@ func TestStatus(t *testing.T) { s.Status("up") assert.Equal(t, StatusUp, s) assert.Equal(t, "✅", s.Emoji()) + s.Status("xxx") + assert.Equal(t, StatusUnknown, s) + s = 10 + assert.Equal(t, "⛔️", s.Emoji()) err := yaml.Unmarshal([]byte("down"), &s) assert.Nil(t, err) @@ -48,7 +100,7 @@ func TestStatus(t *testing.T) { assert.Equal(t, "\"down\"", string(buf)) err = yaml.Unmarshal([]byte("xxx"), &s) - assert.Nil(t, err) + assert.Error(t, err) assert.Equal(t, StatusUnknown, s) err = yaml.Unmarshal([]byte{1, 2}, &s) diff --git a/probe/tcp/tcp.go b/probe/tcp/tcp.go index 0dc4d644..26d2ae43 100644 --- a/probe/tcp/tcp.go +++ b/probe/tcp/tcp.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package tcp is the tcp probe package package tcp import ( diff --git a/probe/tls/tls.go b/probe/tls/tls.go index 46fc58cc..620e5e9c 100644 --- a/probe/tls/tls.go +++ b/probe/tls/tls.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package tls is the tls probe package package tls import ( diff --git a/report/common_test.go b/report/common_test.go index 2258fc46..30675109 100644 --- a/report/common_test.go +++ b/report/common_test.go @@ -18,9 +18,11 @@ package report import ( + "encoding/json" "fmt" "testing" + "bou.ke/monkey" "github.com/stretchr/testify/assert" ) @@ -74,6 +76,14 @@ func TestJSONEscape(t *testing.T) { expected = `hello\\nworld` result = JSONEscape(`hello\nworld`) assert.Equal(t, expected, result) + + monkey.Patch(json.Marshal, func(v interface{}) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + expected = `{hello}` + result = JSONEscape(`{hello}`) + assert.Equal(t, expected, result) + monkey.UnpatchAll() } func TestAutoRefreshJS(t *testing.T) { diff --git a/report/filter_test.go b/report/filter_test.go index 209013fa..402c2d40 100644 --- a/report/filter_test.go +++ b/report/filter_test.go @@ -104,10 +104,19 @@ func TestFilter(t *testing.T) { assert.Equal(t, len(_probes), len(probes)) assert.Equal(t, _probes, probes) + filter.PageSize = -1 + err := filter.Check() + assert.NotNil(t, err) + + filter.PageNum = -1 + err = filter.Check() + assert.NotNil(t, err) + + filter = NewEmptyFilter() // sla >= 60 && sla <= 40 filter.SLAGreater = 60 filter.SLALess = 40 - err := filter.Check() + err = filter.Check() assert.NotNil(t, err) // sla >= 60 && sla <= 200 filter.SLALess = 200 @@ -255,5 +264,4 @@ func TestPage(t *testing.T) { probes = filter.Filter(_probes) assert.Equal(t, 1, len(probes)) assert.Equal(t, _probes[3], probes[0]) - } diff --git a/report/result_test.go b/report/result_test.go index 62d07a94..f2841176 100644 --- a/report/result_test.go +++ b/report/result_test.go @@ -22,10 +22,12 @@ import ( "encoding/json" "fmt" "math/rand" + "reflect" "strings" "testing" "time" + "bou.ke/monkey" "github.com/megaease/easeprobe/global" "github.com/megaease/easeprobe/probe" "github.com/stretchr/testify/assert" @@ -104,6 +106,7 @@ func checkResult(t *testing.T, r probe.Result, rDTO resultDTO) { assert.Equal(t, r.PreStatus, rDTO.PreStatus) assert.Equal(t, r.Message, rDTO.Message) } + func TestResultToJSON(t *testing.T) { r := newDummyResult("dummy") str := ToJSON(r) @@ -115,6 +118,21 @@ func TestResultToJSON(t *testing.T) { err = json.Unmarshal([]byte(str), &rDTO) assert.Nil(t, err) checkResult(t, r, rDTO) + + monkey.Patch(json.Marshal, func(v interface{}) ([]byte, error) { + return nil, fmt.Errorf("marshal error") + }) + str = ToJSON(r) + assert.Empty(t, str) + + monkey.Patch(json.MarshalIndent, func(interface{}, string, string) ([]byte, error) { + return nil, fmt.Errorf("marshal error") + }) + str = ToJSONIndent(r) + assert.Empty(t, str) + + monkey.UnpatchAll() + } func TestResultToHTML(t *testing.T) { @@ -226,4 +244,18 @@ func TestResultToShell(t *testing.T) { assert.Equal(t, data[1][6], FormatTime(r.StartTime)) assert.Equal(t, data[1][7], fmt.Sprintf("%d", r.StartTimestamp)) assert.Equal(t, data[1][8], r.Message) + + var w *csv.Writer + monkey.PatchInstanceMethod(reflect.TypeOf(w), "WriteAll", func(_ *csv.Writer, _ [][]string) error { + return fmt.Errorf("error") + }) + assert.Empty(t, ToCSV(r)) + + monkey.Patch(json.Marshal, func(v any) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + assert.Empty(t, ToShell(r)) + + monkey.UnpatchAll() + } diff --git a/report/sla.go b/report/sla.go index f0eabf1d..be165661 100644 --- a/report/sla.go +++ b/report/sla.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package report is the package for SLA report package report import ( diff --git a/report/sla_test.go b/report/sla_test.go index 60b14dd9..131fbc16 100644 --- a/report/sla_test.go +++ b/report/sla_test.go @@ -18,10 +18,14 @@ package report import ( + "encoding/csv" + "encoding/json" "fmt" "math/rand" + "reflect" "testing" + "bou.ke/monkey" "github.com/megaease/easeprobe/global" "github.com/megaease/easeprobe/probe" "github.com/megaease/easeprobe/probe/base" @@ -72,6 +76,8 @@ func setResultData(probes []probe.Prober) { func TestSLA(t *testing.T) { global.InitEaseProbe("DummyProbe", "icon") probes := getProbers() + probes[0].Result().Status = probe.StatusDown + probe.SetResultData(probes[0].Name(), probes[0].Result()) for f, fn := range FormatFuncs { sla := fn.StatFn(probes) assert.NotEmpty(t, sla) @@ -91,6 +97,50 @@ func TestSLAJSONSection(t *testing.T) { assert.NotEmpty(t, sla) assert.Contains(t, sla, "\"name\":\"probe1\"") assert.Contains(t, sla, "\"status\":\"up\"") + + monkey.Patch(json.Marshal, func(v any) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + sla = SLAJSONSection(p.Result()) + assert.Empty(t, sla) + + sla = SLAJSON([]probe.Prober{p}) + assert.Empty(t, sla) + + monkey.UnpatchAll() +} + +func TestSLAStatusText(t *testing.T) { + p := newDummyProber("probe1") + str := SLAStatusText(p.Probe().Stat, MarkdownSocial) + assert.Contains(t, str, "`") + str = SLAStatusText(p.Probe().Stat, Markdown) + assert.Contains(t, str, "`") + assert.Contains(t, str, "**") + str = SLAStatusText(p.Probe().Stat, HTML) + assert.Contains(t, str, "") + str = SLAStatusText(p.Probe().Stat, Log) + assert.NotContains(t, str, "") + assert.NotContains(t, str, "`") + assert.NotContains(t, str, "**") +} + +func TestFailed(t *testing.T) { + probes := getProbers() + var w *csv.Writer + monkey.PatchInstanceMethod(reflect.TypeOf(w), "WriteAll", func(_ *csv.Writer, _ [][]string) error { + return fmt.Errorf("error") + }) + sla := SLACSV(probes) + assert.Empty(t, sla) + + monkey.Patch(json.Marshal, func(v any) ([]byte, error) { + return nil, fmt.Errorf("error") + }) + sla = SLAShell(probes) + assert.Empty(t, sla) + + monkey.UnpatchAll() } func TestSLAFilter(t *testing.T) { diff --git a/report/types_test.go b/report/types_test.go index 3fc62924..049e4ba8 100644 --- a/report/types_test.go +++ b/report/types_test.go @@ -65,3 +65,10 @@ func TestFormatYAML(t *testing.T) { testFormatYAML(t, SMS, "sms\n") testFormatYAML(t, Unknown, "unknown\n") } + +func TestBadFormat(t *testing.T) { + buf := ([]byte)("- asdf::") + var f Format + err := yaml.Unmarshal(buf, &f) + assert.NotNil(t, err) +} diff --git a/web/server.go b/web/server.go index f7f37d01..1a2a168c 100644 --- a/web/server.go +++ b/web/server.go @@ -15,6 +15,7 @@ * limitations under the License. */ +// Package web is the web server of easeprobe. package web import (