diff --git a/collector/perf_schema_file_instances_test.go b/collector/perf_schema_file_instances_test.go
index 9b14c95..932fdb7 100644
--- a/collector/perf_schema_file_instances_test.go
+++ b/collector/perf_schema_file_instances_test.go
@@ -1,9 +1,9 @@
package collector
import (
+ "fmt"
"testing"
- "fmt"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/smartystreets/goconvey/convey"
diff --git a/mysqld_exporter.go b/mysqld_exporter.go
index c03355c..07532f0 100644
--- a/mysqld_exporter.go
+++ b/mysqld_exporter.go
@@ -132,17 +132,6 @@ var (
dsn string
)
-// landingPage contains the HTML served at '/'.
-// TODO: Make this nicer and more informative.
-var landingPage = []byte(`
-
MySQLd exporter
-
-MySQLd exporter
-Metrics
-
-
-`)
-
func parseMycnf(config interface{}) (string, error) {
var dsn string
cfg, err := ini.Load(config)
@@ -235,6 +224,17 @@ func main() {
kingpin.HelpFlag.Short('h')
kingpin.Parse()
+ // landingPage contains the HTML served at '/'.
+ // TODO: Make this nicer and more informative.
+ var landingPage = []byte(`
+MySQLd exporter
+
+MySQLd exporter
+Metrics
+
+
+`)
+
log.Infoln("Starting mysqld_exporter", version.Info())
log.Infoln("Build context", version.BuildContext())
diff --git a/mysqld_exporter_test.go b/mysqld_exporter_test.go
index be99683..5156e76 100644
--- a/mysqld_exporter_test.go
+++ b/mysqld_exporter_test.go
@@ -1,7 +1,20 @@
package main
import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "reflect"
+ "runtime"
+ "strings"
+ "syscall"
"testing"
+ "time"
"github.com/smartystreets/goconvey/convey"
)
@@ -98,3 +111,159 @@ func TestParseMycnf(t *testing.T) {
})
})
}
+
+// bin stores information about path of executable and attached port
+type bin struct {
+ path string
+ port int
+}
+
+// TestBin builds, runs and tests binary.
+func TestBin(t *testing.T) {
+ var err error
+ binName := "mysqld_exporter"
+
+ binDir, err := ioutil.TempDir("/tmp", binName+"-test-bindir-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err := os.RemoveAll(binDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ importpath := "github.com/prometheus/mysqld_exporter/vendor/github.com/prometheus/common"
+ path := binDir + "/" + binName
+ xVariables := map[string]string{
+ importpath + "/version.Version": "gotest-version",
+ importpath + "/version.Branch": "gotest-branch",
+ importpath + "/version.Revision": "gotest-revision",
+ }
+ var ldflags []string
+ for x, value := range xVariables {
+ ldflags = append(ldflags, fmt.Sprintf("-X %s=%s", x, value))
+ }
+ cmd := exec.Command(
+ "go",
+ "build",
+ "-o",
+ path,
+ "-ldflags",
+ strings.Join(ldflags, " "),
+ )
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err != nil {
+ t.Fatalf("Failed to build: %s", err)
+ }
+
+ tests := []func(*testing.T, bin){
+ testLandingPage,
+ }
+
+ portStart := 56000
+ t.Run(binName, func(t *testing.T) {
+ for _, f := range tests {
+ f := f // capture range variable
+ fName := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
+ portStart++
+ data := bin{
+ path: path,
+ port: portStart,
+ }
+ t.Run(fName, func(t *testing.T) {
+ t.Parallel()
+ f(t, data)
+ })
+ }
+ })
+}
+
+func testLandingPage(t *testing.T, data bin) {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ // Run exporter.
+ cmd := exec.CommandContext(
+ ctx,
+ data.path,
+ "--web.listen-address", fmt.Sprintf(":%d", data.port),
+ )
+ cmd.Env = append(os.Environ(), "DATA_SOURCE_NAME=127.0.0.1:3306")
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ defer cmd.Wait()
+ defer cmd.Process.Kill()
+
+ // Get the main page.
+ urlToGet := fmt.Sprintf("http://127.0.0.1:%d", data.port)
+ body, err := waitForBody(urlToGet)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := string(body)
+
+ expected := `
+MySQLd exporter
+
+MySQLd exporter
+Metrics
+
+
+`
+ if got != expected {
+ t.Fatalf("got '%s' but expected '%s'", got, expected)
+ }
+}
+
+// waitForBody is a helper function which makes http calls until http server is up
+// and then returns body of the successful call.
+func waitForBody(urlToGet string) (body []byte, err error) {
+ tries := 60
+
+ // Get data, but we need to wait a bit for http server.
+ for i := 0; i <= tries; i++ {
+ // Try to get web page.
+ body, err = getBody(urlToGet)
+ if err == nil {
+ return body, err
+ }
+
+ // If there is a syscall.ECONNREFUSED error (web server not available) then retry.
+ if urlError, ok := err.(*url.Error); ok {
+ if opError, ok := urlError.Err.(*net.OpError); ok {
+ if osSyscallError, ok := opError.Err.(*os.SyscallError); ok {
+ if osSyscallError.Err == syscall.ECONNREFUSED {
+ time.Sleep(1 * time.Second)
+ continue
+ }
+ }
+ }
+ }
+
+ // There was an error, and it wasn't syscall.ECONNREFUSED.
+ return nil, err
+ }
+
+ return nil, fmt.Errorf("failed to GET %s after %d tries: %s", urlToGet, tries, err)
+}
+
+// getBody is a helper function which retrieves http body from given address.
+func getBody(urlToGet string) ([]byte, error) {
+ resp, err := http.Get(urlToGet)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ return body, nil
+}