// Copyright The Prometheus Authors
// 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,
// 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.

package procfs

import (
	"reflect"
	"sort"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestSelf(t *testing.T) {
	fs := getProcFixtures(t)

	p1, err := fs.Proc(26231)
	if err != nil {
		t.Fatal(err)
	}
	p2, err := fs.Self()
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(p1, p2) {
		t.Errorf("want process %v, have %v", p1, p2)
	}
}

func TestAllProcs(t *testing.T) {
	procs, err := getProcFixtures(t).AllProcs()
	if err != nil {
		t.Fatal(err)
	}
	sort.Sort(procs)
	for i, p := range []*Proc{{PID: 584}, {PID: 26231}} {
		if want, have := p.PID, procs[i].PID; want != have {
			t.Errorf("want processes %d, have %d", want, have)
		}
	}
}

func TestCmdLine(t *testing.T) {
	for _, tt := range []struct {
		process int
		want    []string
	}{
		{process: 26231, want: []string{"vim", "test.go", "+10"}},
		{process: 26232, want: []string{}},
		{process: 26233, want: []string{"com.github.uiautomator"}},
	} {
		p1, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		c1, err := p1.CmdLine()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, c1); diff != "" {
			t.Fatalf("unexpected cmdline (-want +got):\n%s", diff)
		}
	}
}

func TestWchan(t *testing.T) {
	for _, tt := range []struct {
		process int
		want    string
	}{
		{process: 26231, want: "poll_schedule_timeout"},
		{process: 26232, want: ""},
	} {
		p1, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		c1, err := p1.Wchan()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, c1); diff != "" {
			t.Fatalf("unexpected wchan (-want +got):\n%s", diff)
		}
	}
}

func TestComm(t *testing.T) {
	for _, tt := range []struct {
		process int
		want    string
	}{
		{process: 26231, want: "vim"},
		{process: 26232, want: "ata_sff"},
	} {
		p1, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		c1, err := p1.Comm()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, c1); diff != "" {
			t.Fatalf("unexpected comm (-want +got):\n%s", diff)
		}
	}
}

func TestExecutable(t *testing.T) {
	for _, tt := range []struct {
		process int
		want    string
	}{
		{process: 26231, want: "/usr/bin/vim"},
		{process: 26232, want: ""},
	} {
		p, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		exe, err := p.Executable()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, exe); diff != "" {
			t.Fatalf("unexpected absolute path to exe (-want +got):\n%s", diff)
		}
	}
}

func TestCwd(t *testing.T) {
	for _, tt := range []struct {
		process    int
		want       string
		brokenLink bool
	}{
		{process: 26231, want: "/usr/bin"},
		{process: 26232, want: "/does/not/exist", brokenLink: true},
		{process: 26233, want: ""},
	} {
		p, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		wd, err := p.Cwd()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, wd); diff != "" {
			if wd == "" && tt.brokenLink {
				// Allow the result to be empty when can't os.Readlink broken links
				continue
			}
			t.Fatalf("unexpected absolute path to cwd (-want +got):\n%s", diff)
		}
	}
}

func TestRoot(t *testing.T) {
	for _, tt := range []struct {
		process    int
		want       string
		brokenLink bool
	}{
		{process: 26231, want: "/"},
		{process: 26232, want: "/does/not/exist", brokenLink: true},
		{process: 26233, want: ""},
	} {
		p, err := getProcFixtures(t).Proc(tt.process)
		if err != nil {
			t.Fatal(err)
		}
		rdir, err := p.RootDir()
		if err != nil {
			t.Fatal(err)
		}
		if diff := cmp.Diff(tt.want, rdir); diff != "" {
			if rdir == "" && tt.brokenLink {
				// Allow the result to be empty when can't os.Readlink broken links
				continue
			}
			t.Fatalf("unexpected absolute path to rootdir (-want +got):\n%s", diff)
		}
	}
}

func TestFileDescriptors(t *testing.T) {
	p1, err := getProcFixtures(t).Proc(26231)
	if err != nil {
		t.Fatal(err)
	}
	fds, err := p1.FileDescriptors()
	if err != nil {
		t.Fatal(err)
	}
	sort.Sort(byUintptr(fds))
	if want := []uintptr{0, 1, 2, 3, 10}; !reflect.DeepEqual(want, fds) {
		t.Errorf("want fds %v, have %v", want, fds)
	}
}

func TestFileDescriptorTargets(t *testing.T) {
	p1, err := getProcFixtures(t).Proc(26231)
	if err != nil {
		t.Fatal(err)
	}
	fds, err := p1.FileDescriptorTargets()
	if err != nil {
		t.Fatal(err)
	}
	sort.Strings(fds)
	var want = []string{
		"../../symlinktargets/abc",
		"../../symlinktargets/def",
		"../../symlinktargets/ghi",
		"../../symlinktargets/uvw",
		"../../symlinktargets/xyz",
	}
	if diff := cmp.Diff(want, fds); diff != "" {
		t.Fatalf("unexpected fds (-want +got):\n%s", diff)
	}
}

func TestFileDescriptorsLen(t *testing.T) {
	p1, err := getProcFixtures(t).Proc(26231)
	if err != nil {
		t.Fatal(err)
	}
	l, err := p1.FileDescriptorsLen()
	if err != nil {
		t.Fatal(err)
	}
	if want, have := 5, l; want != have {
		t.Errorf("want fds %d, have %d", want, have)
	}
}

func TestFileDescriptorsInfo(t *testing.T) {
	p1, err := getProcFixtures(t).Proc(26231)
	if err != nil {
		t.Fatal(err)
	}
	fdinfos, err := p1.FileDescriptorsInfo()
	if err != nil {
		t.Fatal(err)
	}
	sort.Sort(fdinfos)
	var want = ProcFDInfos{
		ProcFDInfo{FD: "0", Pos: "0", Flags: "02004000", MntID: "13", InotifyInfos: []InotifyInfo{
			{WD: "3", Ino: "1", Sdev: "34", Mask: "fce"},
			{WD: "2", Ino: "1300016", Sdev: "fd00002", Mask: "fce"},
			{WD: "1", Ino: "2e0001", Sdev: "fd00000", Mask: "fce"},
		}},
		ProcFDInfo{FD: "1", Pos: "0", Flags: "02004002", MntID: "13", InotifyInfos: nil},
		ProcFDInfo{FD: "10", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
		ProcFDInfo{FD: "2", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
		ProcFDInfo{FD: "3", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
	}
	if diff := cmp.Diff(want, fdinfos); diff != "" {
		t.Fatalf("unexpected fdinfos (-want +got):\n%s", diff)
	}
}

type byUintptr []uintptr

func (a byUintptr) Len() int           { return len(a) }
func (a byUintptr) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byUintptr) Less(i, j int) bool { return a[i] < a[j] }
