Implement the verification of query (#10)

diff --git a/commands/root.go b/commands/root.go
index 8493e0d..d378f0c 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -29,6 +29,8 @@
 	"github.com/apache/skywalking-infra-e2e/internal/constant"
 )
 
+var cfg string
+
 // Root represents the base command when called without any subcommands
 var Root = &cobra.Command{
 	Use:           "e2e command [flags]",
@@ -37,7 +39,7 @@
 	SilenceErrors: true,
 	SilenceUsage:  true,
 	PersistentPreRun: func(cmd *cobra.Command, args []string) {
-		config.ReadGlobalConfigFile(constant.E2EDefaultFile)
+		config.ReadGlobalConfigFile(cfg)
 	},
 }
 
@@ -50,5 +52,7 @@
 	Root.AddCommand(verify.Verify)
 	Root.AddCommand(cleanup.Cleanup)
 
+	Root.PersistentFlags().StringVarP(&cfg, "config", "c", constant.E2EDefaultFile, "the config file")
+
 	return Root.Execute()
 }
diff --git a/commands/verify/verify.go b/commands/verify/verify.go
index 91f37bf..05ccbe0 100644
--- a/commands/verify/verify.go
+++ b/commands/verify/verify.go
@@ -21,6 +21,9 @@
 	"fmt"
 
 	"github.com/apache/skywalking-infra-e2e/internal/components/verifier"
+	"github.com/apache/skywalking-infra-e2e/internal/config"
+	"github.com/apache/skywalking-infra-e2e/internal/logger"
+	"github.com/apache/skywalking-infra-e2e/internal/util"
 
 	"github.com/spf13/cobra"
 )
@@ -41,10 +44,62 @@
 	Use:   "verify",
 	Short: "verify if the actual data match the expected data",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		if actual != "" && expected != "" {
-			return verifier.VerifyDataFile(actual, expected)
+		if expected != "" {
+			return verifySingleCase(expected, actual, query)
 		}
-		fmt.Println("Not implemented.")
-		return nil
+		// If there is no given flags.
+		return verifyAccordingConfig()
 	},
 }
+
+func verifySingleCase(expectedFile, actualFile, query string) error {
+	expectedData, err := util.ReadFileContent(expectedFile)
+	if err != nil {
+		return fmt.Errorf("failed to read the expected data file: %v", err)
+	}
+
+	var actualData, sourceName string
+	if actualFile != "" {
+		sourceName = actualFile
+		actualData, err = util.ReadFileContent(actualFile)
+		if err != nil {
+			return fmt.Errorf("failed to read the actual data file: %v", err)
+		}
+	} else if query != "" {
+		sourceName = query
+		actualData, err = util.ExecuteCommand(query)
+		if err != nil {
+			return fmt.Errorf("failed to execute the query: %v", err)
+		}
+	}
+
+	if err = verifier.Verify(actualData, expectedData); err != nil {
+		logger.Log.Warnf("failed to verify the output: %s\n", sourceName)
+		if me, ok := err.(*verifier.MismatchError); ok {
+			fmt.Println(me.Error())
+		}
+	} else {
+		logger.Log.Infof("verified the output: %s\n", sourceName)
+	}
+	return nil
+}
+
+// verifyAccordingConfig reads cases from the config file and verifies them.
+func verifyAccordingConfig() error {
+	if config.GlobalConfig.Error != nil {
+		return config.GlobalConfig.Error
+	}
+
+	e2eConfig := config.GlobalConfig.E2EConfig
+
+	for _, v := range e2eConfig.Verify {
+		if v.Expected != "" {
+			if err := verifySingleCase(v.Expected, v.Actual, v.Query); err != nil {
+				logger.Log.Errorf("%v", err)
+			}
+		} else {
+			logger.Log.Error("the expected data file is not specified")
+		}
+	}
+	return nil
+}
diff --git a/examples/simple/e2e.yaml b/examples/simple/e2e.yaml
index 181cd9c..721d446 100644
--- a/examples/simple/e2e.yaml
+++ b/examples/simple/e2e.yaml
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# This file is used to show how to write configuration files and can be used to test.
+
 setup:
   env: kind
   file: kind.yaml
@@ -32,3 +34,13 @@
           resource: pod
           for: condition=Ready
   timeout: 600
+
+verify:
+  - actual: ../../test/verify/1.actual.yaml
+    expected: ../../test/verify/1.expected.yaml
+  - actual: ../../test/verify/2.actual.yaml
+    expected: ../../test/verify/2.expected.yaml
+  - actual: ../../test/verify/1.actual.yaml
+    expected: ../../test/verify/2.expected.yaml
+  - query: swctl --display yaml service ls
+    expected: ../../test/verify/3.expected.yaml
diff --git a/internal/components/verifier/verifier.go b/internal/components/verifier/verifier.go
index 2f4675a..866b063 100644
--- a/internal/components/verifier/verifier.go
+++ b/internal/components/verifier/verifier.go
@@ -14,25 +14,24 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+
 package verifier
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 
-	"github.com/apache/skywalking-infra-e2e/internal/logger"
-	"github.com/apache/skywalking-infra-e2e/internal/util"
 	"github.com/apache/skywalking-infra-e2e/third-party/go/template"
 
 	"github.com/google/go-cmp/cmp"
 	"gopkg.in/yaml.v2"
 )
 
-// MismatchError is the error type returned by the verify functions.
-// Then the caller will know if there is a mismatch.
+// MismatchError is the error type returned by the Verify functions.
+// It contains the diff content.
 type MismatchError struct {
-	Err error
+	Err  error
+	diff string
 }
 
 func (e *MismatchError) Unwrap() error { return e.Err }
@@ -41,65 +40,35 @@
 	if e == nil {
 		return "<nil>"
 	}
-	return "the actual data does not match the expected data"
+	return e.diff
 }
 
-// VerifyDataFile reads the actual data from the file and verifies.
-func VerifyDataFile(actualFile, expectedFile string) error {
-	actualData, err := util.ReadFileContent(actualFile)
-	if err != nil {
-		logger.Log.Error("failed to read the actual data file")
-		return err
-	}
-
-	expectedTemplate, err := util.ReadFileContent(expectedFile)
-	if err != nil {
-		logger.Log.Error("failed to read the expected data file")
-		return err
-	}
-
-	return verify(actualData, expectedTemplate)
-}
-
-// VerifyQuery gets the actual data from the query and then verifies.
-func VerifyQuery(query, expectedFile string) error {
-	return errors.New("not implemented")
-}
-
-// verify checks if the actual data match the expected template.
-// It will print the diff if the actual data does not match.
-func verify(actualData, expectedTemplate string) error {
+// Verify checks if the actual data match the expected template.
+func Verify(actualData, expectedTemplate string) error {
 	var actual interface{}
 	if err := yaml.Unmarshal([]byte(actualData), &actual); err != nil {
-		logger.Log.Error("failed to unmarshal actual data")
-		return err
+		return fmt.Errorf("failed to unmarshal actual data: %v", err)
 	}
 
 	tmpl, err := template.New("test").Funcs(funcMap()).Parse(expectedTemplate)
 	if err != nil {
-		logger.Log.Error("failed to parse template")
-		return err
+		return fmt.Errorf("failed to parse template: %v", err)
 	}
 
 	var b bytes.Buffer
 	if err := tmpl.Execute(&b, actual); err != nil {
-		logger.Log.Error("failed to execute template")
-		return err
+		return fmt.Errorf("failed to execute template: %v", err)
 	}
 
 	var expected interface{}
 	if err := yaml.Unmarshal(b.Bytes(), &expected); err != nil {
-		logger.Log.Error("failed to unmarshal expected data")
-		return err
+		return fmt.Errorf("failed to unmarshal expected data: %v", err)
 	}
 
 	if !cmp.Equal(expected, actual) {
 		// TODO: use a custom Reporter (suggested by the comment of cmp.Diff)
 		diff := cmp.Diff(expected, actual)
-		fmt.Println(diff)
-		return &MismatchError{}
+		return &MismatchError{diff: diff}
 	}
-
-	logger.Log.Info("the actual data matches the expected data")
 	return nil
 }
diff --git a/internal/components/verifier/verifier_test.go b/internal/components/verifier/verifier_test.go
index f7d073a..0d6b7e7 100644
--- a/internal/components/verifier/verifier_test.go
+++ b/internal/components/verifier/verifier_test.go
@@ -142,8 +142,8 @@
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if err := verify(tt.args.actualData, tt.args.expectedTemplate); (err != nil) != tt.wantErr {
-				t.Errorf("verify() error = %v, wantErr %v", err, tt.wantErr)
+			if err := Verify(tt.args.actualData, tt.args.expectedTemplate); (err != nil) != tt.wantErr {
+				t.Errorf("Verify() error = %v, wantErr %v", err, tt.wantErr)
 			}
 		})
 	}
diff --git a/internal/config/globalConfig.go b/internal/config/globalConfig.go
index f8c1334..c796962 100644
--- a/internal/config/globalConfig.go
+++ b/internal/config/globalConfig.go
@@ -22,9 +22,10 @@
 	"fmt"
 	"io/ioutil"
 
-	"gopkg.in/yaml.v2"
-
+	"github.com/apache/skywalking-infra-e2e/internal/logger"
 	"github.com/apache/skywalking-infra-e2e/internal/util"
+
+	"gopkg.in/yaml.v2"
 )
 
 // GlobalE2EConfig store E2EConfig which can be used globally.
@@ -57,4 +58,5 @@
 
 	GlobalConfig.E2EConfig = e2eConfigObject
 	GlobalConfig.Error = nil
+	logger.Log.Info("load the e2e config successfully")
 }
diff --git a/internal/util/utils.go b/internal/util/utils.go
index 0c2a2f0..fc5bda4 100644
--- a/internal/util/utils.go
+++ b/internal/util/utils.go
@@ -19,6 +19,7 @@
 package util
 
 import (
+	"bytes"
 	"errors"
 	"io/ioutil"
 	"os"
@@ -52,3 +53,18 @@
 	}
 	return "", errors.New("the file does not exist")
 }
+
+// ExecuteCommand executes the given command and returns the result.
+func ExecuteCommand(cmd string) (string, error) {
+	command := exec.Command("bash", "-c", cmd)
+	outinfo := bytes.Buffer{}
+	command.Stdout = &outinfo
+
+	if err := command.Start(); err != nil {
+		return "", err
+	}
+	if err := command.Wait(); err != nil {
+		return "", err
+	}
+	return outinfo.String(), nil
+}
diff --git a/test/verify/3.expected.yaml b/test/verify/3.expected.yaml
new file mode 100644
index 0000000..40f5133
--- /dev/null
+++ b/test/verify/3.expected.yaml
@@ -0,0 +1,22 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you 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, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+{{- contains . }}
+- id: {{ notEmpty .id }}
+  name: {{ notEmpty .name }}
+  group: ""
+{{- end }}