@ 261102.1
current
package fzf

import (
	"os"
	"path/filepath"
	"slices"
	"sync"
	"testing"
	"time"

	"github.com/junegunn/fzf/src/util"
)

func TestReadFromCommand(t *testing.T) {
	strs := []string{}
	eb := util.NewEventBox()
	exec := util.NewExecutor("")
	reader := NewReader(
		func(s []byte) bool { strs = append(strs, string(s)); return true },
		eb, exec, false, true)

	reader.startEventPoller()

	// Check EventBox
	if eb.Peek(EvtReadNew) {
		t.Error("EvtReadNew should not be set yet")
	}

	// Normal command
	counter := 0
	ready := func() {
		counter++
	}
	reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
	if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
		t.Errorf("%s", strs)
	}

	// Check EventBox again
	eb.WaitFor(EvtReadFin)

	// Wait should return immediately
	eb.Wait(func(events *util.Events) {
		events.Clear()
	})

	// EventBox is cleared
	if eb.Peek(EvtReadNew) {
		t.Error("EvtReadNew should not be set yet")
	}

	// Make sure that event poller is finished
	time.Sleep(readerPollIntervalMax)

	// Restart event poller
	reader.startEventPoller()

	// Failing command
	reader.fin(reader.readFromCommand(`no-such-command`, nil, ready))
	strs = []string{}
	if len(strs) > 0 || counter != 2 {
		t.Errorf("%s", strs)
	}

	// Check EventBox again
	if eb.Peek(EvtReadNew) {
		t.Error("Command failed. EvtReadNew should not be set")
	}
	if !eb.Peek(EvtReadFin) {
		t.Error("EvtReadFin should be set")
	}
}

func walkFiles(t *testing.T, roots ...string) []string {
	t.Helper()
	var mu sync.Mutex
	files := []string{}
	reader := NewReader(func(data []byte) bool {
		mu.Lock()
		defer mu.Unlock()
		files = append(files, filepath.FromSlash(string(data)))
		return true
	}, util.NewEventBox(), nil, false, false)
	if !reader.readFiles(roots, walkerOpts{file: true, follow: true}, nil) {
		t.Fatal("file walk failed")
	}
	slices.Sort(files)
	return files
}

func TestReadFilesSymlinkAcrossRoots(t *testing.T) {
	base := t.TempDir()
	app := filepath.Join(base, "app")
	shared := filepath.Join(base, "shared")
	frontend := filepath.Join(shared, "frontend")
	os.MkdirAll(app, 0755)
	os.MkdirAll(frontend, 0755)
	os.WriteFile(filepath.Join(shared, "docs.txt"), nil, 0644)
	os.WriteFile(filepath.Join(frontend, "index.js"), nil, 0644)
	if err := os.Symlink(filepath.Join("..", "shared"), filepath.Join(app, "shared")); err != nil {
		t.Skipf("cannot create symlink: %v", err)
	}

	viaApp := []string{
		filepath.Join(app, "shared", "docs.txt"),
		filepath.Join(app, "shared", "frontend", "index.js"),
	}
	both := append(slices.Clone(viaApp), filepath.Join(frontend, "index.js"))
	slices.Sort(both)
	for _, tc := range []struct {
		roots []string
		want  []string
	}{
		{[]string{app}, viaApp},
		{[]string{app, frontend}, both},
		{[]string{frontend, app}, both},
	} {
		if files := walkFiles(t, tc.roots...); !slices.Equal(files, tc.want) {
			t.Errorf("roots = %v, files = %v, want %v", tc.roots, files, tc.want)
		}
	}
}

func TestReadFilesSkipsAncestorSymlinks(t *testing.T) {
	base := t.TempDir()
	project := filepath.Join(base, "project")
	os.Mkdir(project, 0755)
	file := filepath.Join(project, "file.txt")
	os.WriteFile(file, nil, 0644)
	os.WriteFile(filepath.Join(base, "outside.txt"), nil, 0644)
	for name, target := range map[string]string{"parent": "..", "self": "."} {
		if err := os.Symlink(target, filepath.Join(project, name)); err != nil {
			t.Skipf("cannot create symlink: %v", err)
		}
	}
	if files := walkFiles(t, project); !slices.Equal(files, []string{file}) {
		t.Errorf("files = %v, want [%s]", files, file)
	}
}