diff --git a/cmd/alp/cmd/common_test.go b/cmd/alp/cmd/common_test.go index 275aae5..f87a116 100644 --- a/cmd/alp/cmd/common_test.go +++ b/cmd/alp/cmd/common_test.go @@ -10,22 +10,22 @@ import ( func TestCommonFlags(t *testing.T) { tempDir := t.TempDir() - tempLog, err := testutil.CreateTempDirAndFile(tempDir, testutil.JsonLog(testutil.NewJsonLogKeys())) + tempLog, err := testutil.CreateTempDirAndFile(tempDir, "test_common_flags_temp_log", testutil.JsonLog(testutil.NewJsonLogKeys())) if err != nil { t.Fatal(err) } - tempConfig, err := testutil.CreateTempDirAndFile(tempDir, testutil.ConfigFile()) + tempConfig, err := testutil.CreateTempDirAndFile(tempDir, "test_common_flags_temp_config", testutil.ConfigFile()) if err != nil { t.Fatal(err) } - tempPos, err := testutil.CreateTempDirAndFile(tempDir, "") + tempPos, err := testutil.CreateTempDirAndFile(tempDir, "test_common_flags_temp_pos", "") if err != nil { t.Fatal(err) } - tempDump, err := testutil.CreateTempDirAndFile(tempDir, "") + tempDump, err := testutil.CreateTempDirAndFile(tempDir, "test_common_flags_temp_dump", "") if err != nil { t.Fatal(err) } diff --git a/cmd/alp/cmd/flags_test.go b/cmd/alp/cmd/flags_test.go new file mode 100644 index 0000000..d9e8ad1 --- /dev/null +++ b/cmd/alp/cmd/flags_test.go @@ -0,0 +1,135 @@ +package cmd + +import ( + "strings" + "testing" + + "github.com/spf13/viper" + + "github.com/google/go-cmp/cmp" + "github.com/tkuchiki/alp/internal/testutil" + "github.com/tkuchiki/alp/options" +) + +func Test_createOptionsFromConfig(t *testing.T) { + viper.Reset() + rootCmd := NewRootCmd("test") + flags := newFlags() + + flags.defineProfileOptions(rootCmd) + flags.defineJSONOptions(rootCmd) + flags.defineLTSVOptions(rootCmd) + flags.defineRegexpOptions(rootCmd) + flags.definePcapOptions(rootCmd) + flags.defineCountKeys(rootCmd) + + tempDir := t.TempDir() + sort := "max" + dummyOpts := testutil.DummyOptions(sort) + + var err error + flags.config, err = testutil.CreateTempDirAndFile(tempDir, "test_create_options_from_config_config", testutil.DummyConfigFile(sort, dummyOpts)) + if err != nil { + t.Fatal(err) + } + + var opts *options.Options + opts, err = flags.createOptionsFromConfig(rootCmd) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(dummyOpts, opts); diff != "" { + t.Errorf("%s", diff) + } +} + +func Test_createOptionsFromConfig_overwrite(t *testing.T) { + rootCmd := NewRootCmd("test") + flags := newFlags() + + flags.defineProfileOptions(rootCmd) + flags.defineJSONOptions(rootCmd) + flags.defineLTSVOptions(rootCmd) + flags.defineRegexpOptions(rootCmd) + flags.definePcapOptions(rootCmd) + flags.defineCountKeys(rootCmd) + + tempDir := t.TempDir() + sort := "max" + + overwrittenSort := "min" + overwrittenOpts := testutil.DummyOverwrittenOptions(overwrittenSort) + + var err error + flags.config, err = testutil.CreateTempDirAndFile(tempDir, "test_create_options_from_config_overwrite_config", testutil.DummyConfigFile(sort, overwrittenOpts)) + if err != nil { + t.Fatal(err) + } + + viper.Set("file", overwrittenOpts.File) + viper.Set("dump", overwrittenOpts.Dump) + viper.Set("load", overwrittenOpts.Load) + viper.Set("sort", overwrittenSort) + viper.Set("reverse", overwrittenOpts.Reverse) + viper.Set("query_string", overwrittenOpts.QueryString) + viper.Set("query_string_ignore_values", overwrittenOpts.QueryStringIgnoreValues) + viper.Set("decode_uri", overwrittenOpts.DecodeUri) + viper.Set("format", overwrittenOpts.Format) + viper.Set("noheaders", overwrittenOpts.NoHeaders) + viper.Set("show_footers", overwrittenOpts.ShowFooters) + viper.Set("limit", overwrittenOpts.Limit) + viper.Set("matching_groups", strings.Join(overwrittenOpts.MatchingGroups, ",")) + viper.Set("filters", overwrittenOpts.Filters) + viper.Set("pos_file", overwrittenOpts.PosFile) + viper.Set("nosave_pos", overwrittenOpts.NoSavePos) + viper.Set("location", overwrittenOpts.Location) + viper.Set("output", overwrittenOpts.Output) + viper.Set("percentiles", testutil.IntSliceToString(overwrittenOpts.Percentiles)) + viper.Set("pagination_limit", overwrittenOpts.PaginationLimit) + + // json + viper.Set("json.uri_key", overwrittenOpts.JSON.UriKey) + viper.Set("json.method_key", overwrittenOpts.JSON.MethodKey) + viper.Set("json.time_key", overwrittenOpts.JSON.TimeKey) + viper.Set("json.restime_key", overwrittenOpts.JSON.ResponseTimeKey) + viper.Set("json.reqtime_key", overwrittenOpts.JSON.RequestTimeKey) + viper.Set("json.body_bytes_key", overwrittenOpts.JSON.BodyBytesKey) + viper.Set("json.status_key", overwrittenOpts.JSON.StatusKey) + + // ltsv + viper.Set("ltsv.uri_label", overwrittenOpts.LTSV.UriLabel) + viper.Set("ltsv.method_label", overwrittenOpts.LTSV.MethodLabel) + viper.Set("ltsv.time_label", overwrittenOpts.LTSV.TimeLabel) + viper.Set("ltsv.apptime_label", overwrittenOpts.LTSV.ApptimeLabel) + viper.Set("ltsv.reqtime_label", overwrittenOpts.LTSV.ReqtimeLabel) + viper.Set("ltsv.size_label", overwrittenOpts.LTSV.SizeLabel) + viper.Set("ltsv.status_label", overwrittenOpts.LTSV.StatusLabel) + + // regexp + viper.Set("regexp.pattern", overwrittenOpts.Regexp.Pattern) + viper.Set("regexp.uri_subexp", overwrittenOpts.Regexp.UriSubexp) + viper.Set("regexp.method_subexp", overwrittenOpts.Regexp.MethodSubexp) + viper.Set("regexp.time_subexp", overwrittenOpts.Regexp.TimeSubexp) + viper.Set("regexp.restime_subexp", overwrittenOpts.Regexp.ResponseTimeSubexp) + viper.Set("regexp.reqtime_subexp", overwrittenOpts.Regexp.RequestTimeSubexp) + viper.Set("regexp.body_bytes_subexp", overwrittenOpts.Regexp.BodyBytesSubexp) + viper.Set("regexp.status_subexp", overwrittenOpts.Regexp.StatusSubexp) + + // pcap + viper.Set("pcap.server_ips", strings.Join(overwrittenOpts.Pcap.ServerIPs, ",")) + viper.Set("pcap.server_port", overwrittenOpts.Pcap.ServerPort) + + // count + viper.Set("count.keys", overwrittenOpts.Count.Keys) + + var opts *options.Options + opts, err = flags.createOptionsFromConfig(rootCmd) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(overwrittenOpts, opts); diff != "" { + t.Errorf("%s", diff) + } +} diff --git a/cmd/alp/cmd/json_test.go b/cmd/alp/cmd/json_test.go index 29fe726..f630f3d 100644 --- a/cmd/alp/cmd/json_test.go +++ b/cmd/alp/cmd/json_test.go @@ -19,7 +19,7 @@ func TestJSONCmd(t *testing.T) { jsonLog := testutil.JsonLog(keys) - tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), jsonLog) + tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), "test_json_cmd_temp_file", jsonLog) if err != nil { t.Fatal(err) } diff --git a/cmd/alp/cmd/ltsv_test.go b/cmd/alp/cmd/ltsv_test.go index 809c917..8745627 100644 --- a/cmd/alp/cmd/ltsv_test.go +++ b/cmd/alp/cmd/ltsv_test.go @@ -19,7 +19,7 @@ func TestLTSVCmd(t *testing.T) { ltsvLog := testutil.LTSVLog(keys) - tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), ltsvLog) + tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), "test_ltsv_cmd_temp_file", ltsvLog) if err != nil { t.Fatal(err) } diff --git a/cmd/alp/cmd/regexp_test.go b/cmd/alp/cmd/regexp_test.go index 199bdac..1a5b837 100644 --- a/cmd/alp/cmd/regexp_test.go +++ b/cmd/alp/cmd/regexp_test.go @@ -19,7 +19,7 @@ func TestRegexpCmd(t *testing.T) { regexpLog := testutil.RegexpLog() - tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), regexpLog) + tempFile, err := testutil.CreateTempDirAndFile(t.TempDir(), "test_regexp_cmd_temp_file", regexpLog) if err != nil { t.Fatal(err) } diff --git a/go.mod b/go.mod index 3082bf0..dc59b57 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tkuchiki/alp require ( github.com/Songmu/go-ltsv v0.0.0-20200903131950-a608c3f6a014 github.com/antonmedv/expr v1.8.9 + github.com/google/go-cmp v0.5.9 github.com/google/gopacket v1.1.19 github.com/kylelemons/godebug v1.1.0 github.com/olekukonko/tablewriter v0.0.5 diff --git a/internal/testutil/cmd.go b/internal/testutil/cmd.go index 1786cba..e4415b2 100644 --- a/internal/testutil/cmd.go +++ b/internal/testutil/cmd.go @@ -1,11 +1,14 @@ package testutil import ( - "fmt" + "bytes" "os" "path/filepath" "strings" - "time" + "text/template" + + "github.com/tkuchiki/alp/options" + "github.com/tkuchiki/alp/stats" ) type LogKeys struct { @@ -42,8 +45,8 @@ func NewLTSVLogKeys() LogKeys { } } -func CreateTempDirAndFile(dir, content string) (string, error) { - fpath := filepath.Join(dir, fmt.Sprint(time.Now().UnixNano())) +func CreateTempDirAndFile(dir, filename, content string) (string, error) { + fpath := filepath.Join(dir, filename) err := os.WriteFile(fpath, []byte(content), 0644) return fpath, err @@ -108,3 +111,213 @@ func ConfigFile() string { reverse: true query_string: true` } + +func DummyOptions(sort string) *options.Options { + sortOptions := stats.NewSortOptions() + sortOptions.SetAndValidate(sort) + + return &options.Options{ + File: "/path/to/file", + Sort: sortOptions.SortType(), + Location: "dummy", + Reverse: false, + QueryString: false, + QueryStringIgnoreValues: false, + DecodeUri: false, + Format: "markdown", + Limit: 100, + NoHeaders: false, + ShowFooters: false, + MatchingGroups: []string{ + "/foo/.+", + }, + Filters: ".Uri == '/foo/bar'", + Output: "count,uri,min,max", + PosFile: "/path/to/pos", + NoSavePos: false, + Percentiles: []int{1, 5}, + PaginationLimit: 10, + LTSV: &options.LTSVOptions{ + UriLabel: "u", + MethodLabel: "m", + TimeLabel: "t", + ApptimeLabel: "a", + ReqtimeLabel: "r", + SizeLabel: "sz", + StatusLabel: "st", + }, + JSON: &options.JSONOptions{ + UriKey: "u", + MethodKey: "m", + TimeKey: "t", + ResponseTimeKey: "res", + RequestTimeKey: "req", + BodyBytesKey: "b", + StatusKey: "s", + }, + Regexp: &options.RegexpOptions{ + Pattern: "dummy pattern", + UriSubexp: "u", + MethodSubexp: "m", + TimeSubexp: "t", + ResponseTimeSubexp: "res", + RequestTimeSubexp: "req", + BodyBytesSubexp: "b", + StatusSubexp: "s", + }, + Pcap: &options.PcapOptions{ + ServerIPs: []string{ + "192.168.1.10", + }, + ServerPort: 12345, + }, + Count: &options.CountOptions{ + Keys: []string{ + "ua", + }, + }, + } +} + +func DummyOverwrittenOptions(sort string) *options.Options { + sortOptions := stats.NewSortOptions() + sortOptions.SetAndValidate(sort) + + return &options.Options{ + File: "/path/to/overwritten/file", + Sort: sortOptions.SortType(), + Location: "overwritten location", + Reverse: true, + QueryString: true, + QueryStringIgnoreValues: true, + DecodeUri: true, + Format: "tsv", + Limit: 200, + NoHeaders: true, + ShowFooters: true, + MatchingGroups: []string{ + "/foo/bar/.+", + "/bar/.+", + }, + Filters: ".Status == 200", + Output: "uri,avg", + PosFile: "/path/to/overwritten/pos", + NoSavePos: true, + Percentiles: []int{5, 9}, + PaginationLimit: 20, + LTSV: &options.LTSVOptions{ + UriLabel: "u2", + MethodLabel: "m2", + TimeLabel: "t2", + ApptimeLabel: "a2", + ReqtimeLabel: "r2", + SizeLabel: "sz2", + StatusLabel: "st2", + }, + JSON: &options.JSONOptions{ + UriKey: "u2", + MethodKey: "m2", + TimeKey: "t2", + ResponseTimeKey: "res2", + RequestTimeKey: "req2", + BodyBytesKey: "b2", + StatusKey: "s2", + }, + Regexp: &options.RegexpOptions{ + UriSubexp: "u2", + MethodSubexp: "m2", + TimeSubexp: "t2", + ResponseTimeSubexp: "res2", + RequestTimeSubexp: "req2", + BodyBytesSubexp: "b2", + StatusSubexp: "s2", + }, + Pcap: &options.PcapOptions{ + ServerIPs: []string{ + "192.168.1.20", + }, + ServerPort: 54321, + }, + Count: &options.CountOptions{ + Keys: []string{ + "host", + "user_agent", + }, + }, + } +} + +func DummyConfigFile(sort string, dummyOpts *options.Options) string { + configTmpl := `file: {{ .File }} +sort: ` + sort + ` +reverse: {{ .Reverse }} +query_string: {{ .QueryString }} +query_string_ignore_values: {{ .QueryStringIgnoreValues }} +decode_uri: {{ .DecodeUri }} +format: {{ .Format }} +limit: {{ .Limit }} +noheaders: {{ .NoHeaders }} +show_footers: {{ .ShowFooters }} +matching_groups: +{{ range .MatchingGroups }} + - {{ . }} +{{ end }} +filters: {{ .Filters }} +output: {{ .Output }} +pos_file: {{ .PosFile }} +nosave_pos: {{ .NoSavePos }} +location: {{ .Location }} +percentiles: +{{ range .Percentiles }} + - {{ . }} +{{ end }} +pagination_limit: {{ .PaginationLimit }} +ltsv: + uri_label: {{ .LTSV.UriLabel }} + method_label: {{ .LTSV.MethodLabel }} + time_label: {{ .LTSV.TimeLabel }} + apptime_label: {{ .LTSV.ApptimeLabel }} + reqtime_label: {{ .LTSV.ReqtimeLabel }} + size_label: {{ .LTSV.SizeLabel }} + status_label: {{ .LTSV.StatusLabel }} +json: + uri_key: {{ .JSON.UriKey }} + method_key: {{ .JSON.MethodKey }} + time_key: {{ .JSON.TimeKey }} + response_time_key: {{ .JSON.ResponseTimeKey }} + request_time_key: {{ .JSON.RequestTimeKey }} + body_bytes_key: {{ .JSON.BodyBytesKey }} + status_key: {{ .JSON.StatusKey }} +regexp: + pattern: {{ .Regexp.Pattern }} + uri_subexp: {{ .Regexp.UriSubexp }} + method_subexp: {{ .Regexp.MethodSubexp }} + time_subexp: {{ .Regexp.TimeSubexp }} + response_time_subexp: {{ .Regexp.ResponseTimeSubexp }} + request_time_subexp: {{ .Regexp.RequestTimeSubexp }} + body_bytes_subexp: {{ .Regexp.BodyBytesSubexp }} + status_subexp: {{ .Regexp.StatusSubexp }} +pcap: + server_ips: +{{ range .Pcap.ServerIPs }} + - {{ . }} +{{ end }} + server_port: {{ .Pcap.ServerPort }} +count: + keys: +{{ range .Count.Keys }} + - {{ . }} +{{ end }} +` + t, err := template.New("dummy_config").Parse(configTmpl) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + if err = t.Execute(&buf, dummyOpts); err != nil { + panic(err) + } + + return buf.String() +} diff --git a/internal/testutil/strconv.go b/internal/testutil/strconv.go new file mode 100644 index 0000000..df19f34 --- /dev/null +++ b/internal/testutil/strconv.go @@ -0,0 +1,10 @@ +package testutil + +import ( + "fmt" + "strings" +) + +func IntSliceToString(si []int) string { + return strings.Trim(strings.Replace(fmt.Sprint(si), " ", ",", -1), "[]") +}