<commit_before>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/peferron/alternate/testbin"
)

const (
	zero  time.Duration = 0
	one                 = 10 * time.Millisecond
	two                 = 2 * one
	three               = 3 * one
)

func init() {
	fmt.Println("Initializing testExit channel")
	testExit = make(chan struct{})
}

func newNilWriter() *nilWriter {
	return &nilWriter{}
}

type nilWriter struct{}

func (w *nilWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

func newLineWriter(log bool) *lineWriter {
	return &lineWriter{
		"",
		[]string{},
		&sync.Mutex{},
		log,
	}
}

type lineWriter struct {
	buf   string
	lines []string
	mutex *sync.Mutex
	log   bool
}

func (w *lineWriter) Write(p []byte) (n int, err error) {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	n = len(p)

	w.buf += string(p)
	a := strings.Split(w.buf, "\n")
	for _, s := range a {
		if s == "" {
			continue
		}
		w.lines = append(w.lines, s)
		if w.log {
			fmt.Printf("LineWriter received: %q\n", s)
		}
	}
	w.buf = a[len(a)-1]

	return
}

func (w *lineWriter) reset() {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	w.buf = ""
	w.lines = []string{}
}

func (w *lineWriter) getLines() []string {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	clone := make([]string, len(w.lines))
	for i, s := range w.lines {
		clone[i] = s
	}
	return clone
}

type test struct {
	t           *testing.T
	cmdStdout   *lineWriter
	cmdStderr   *lineWriter
	params      []string
	expectIndex int
}

func (test *test) reset() {
	test.cmdStdout.reset()
	test.cmdStderr.reset()
}

func (test *test) expect(d time.Duration, lines []string) {
	expectIndex := test.expectIndex
	test.expectIndex++

	time.Sleep(d)

	stdoutLines := test.cmdStdout.getLines()
	if !sameStrings(lines, stdoutLines) {
		fmt.Printf("For parameters %q expect #%d, within %v expected lines %q in stdout, was %q\n",
			test.params, expectIndex, d, lines, stdoutLines)
		test.t.Errorf("For parameters %q expect #%d, within %v expected lines %q in stdout, was %q",
			test.params, expectIndex, d, lines, stdoutLines)
	}
	stderrLines := test.cmdStderr.getLines()
	if !sameStrings(lines, stderrLines) {
		fmt.Printf("For parameters %q expect #%d, within %v expected lines %q in stderr, was %q\n",
			test.params, expectIndex, d, lines, stderrLines)
		test.t.Errorf("For parameters %q expect #%d, within %v expected lines %q in stderr, was %q",
			test.params, expectIndex, d, lines, stderrLines)
	}
}

// sameStrings makes an unordered comparison of two slices of strings, and returns true if they
// contain the same strings, or false otherwise.
func sameStrings(a []string, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	if len(a) == 0 {
		return true
	}
	aa := clone(a)
	bb := clone(b)
	sort.Strings(aa)
	sort.Strings(bb)
	for i := range aa {
		if aa[i] != bb[i] {
			return false
		}
	}
	return true
}

// clone returns a deep copy of a slice of strings.
func clone(a []string) []string {
	b := make([]string, len(a))
	for i := range a {
		b[i] = a[i]
	}
	return b
}

func start(t *testing.T, params []string, overlap time.Duration) *test {
	test := &test{
		t,
		newLineWriter(false),
		newLineWriter(false),
		params,
		0,
	}
	c := testbin.Build() + " " + placeholder
	go alternate(c, placeholder, params, overlap, newNilWriter(), test.cmdStdout, test.cmdStderr)
	return test
}

func sendUsr1() {
	process().Signal(syscall.SIGUSR1)
}

func stop() {
	testExit <- struct{}{}
	time.Sleep(one)
}

func process() *os.Process {
	p, err := os.FindProcess(os.Getpid())
	if err != nil {
		panic(err)
	}
	return p
}

func TestLineWriter(t *testing.T) {
	type action struct {
		reset bool
		write string
	}
	tests := []struct {
		actions []action
		lines   []string<commit_msg>Bring back TestSameStrings wrongfully removed in commit <PASSWORD><commit_after>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/peferron/alternate/testbin"
)

const (
	zero  time.Duration = 0
	one                 = 10 * time.Millisecond
	two                 = 2 * one
	three               = 3 * one
)

func init() {
	fmt.Println("Initializing testExit channel")
	testExit = make(chan struct{})
}

func newNilWriter() *nilWriter {
	return &nilWriter{}
}

type nilWriter struct{}

func (w *nilWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

func newLineWriter(log bool) *lineWriter {
	return &lineWriter{
		"",
		[]string{},
		&sync.Mutex{},
		log,
	}
}

type lineWriter struct {
	buf   string
	lines []string
	mutex *sync.Mutex
	log   bool
}

func (w *lineWriter) Write(p []byte) (n int, err error) {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	n = len(p)

	w.buf += string(p)
	a := strings.Split(w.buf, "\n")
	for _, s := range a {
		if s == "" {
			continue
		}
		w.lines = append(w.lines, s)
		if w.log {
			fmt.Printf("LineWriter received: %q\n", s)
		}
	}
	w.buf = a[len(a)-1]

	return
}

func (w *lineWriter) reset() {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	w.buf = ""
	w.lines = []string{}
}

func (w *lineWriter) getLines() []string {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	clone := make([]string, len(w.lines))
	for i, s := range w.lines {
		clone[i] = s
	}
	return clone
}

type test struct {
	t           *testing.T
	cmdStdout   *lineWriter
	cmdStderr   *lineWriter
	params      []string
	expectIndex int
}

func (test *test) reset() {
	test.cmdStdout.reset()
	test.cmdStderr.reset()
}

func (test *test) expect(d time.Duration, lines []string) {
	expectIndex := test.expectIndex
	test.expectIndex++

	time.Sleep(d)

	stdoutLines := test.cmdStdout.getLines()
	if !sameStrings(lines, stdoutLines) {
		fmt.Printf("For parameters %q expect #%d, within %v expected lines %q in stdout, was %q\n",
			test.params, expectIndex, d, lines, stdoutLines)
		test.t.Errorf("For parameters %q expect #%d, within %v expected lines %q in stdout, was %q",
			test.params, expectIndex, d, lines, stdoutLines)
	}
	stderrLines := test.cmdStderr.getLines()
	if !sameStrings(lines, stderrLines) {
		fmt.Printf("For parameters %q expect #%d, within %v expected lines %q in stderr, was %q\n",
			test.params, expectIndex, d, lines, stderrLines)
		test.t.Errorf("For parameters %q expect #%d, within %v expected lines %q in stderr, was %q",
			test.params, expectIndex, d, lines, stderrLines)
	}
}

// sameStrings makes an unordered comparison of two slices of strings, and returns true if they
// contain the same strings, or false otherwise.
func sameStrings(a []string, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	if len(a) == 0 {
		return true
	}
	aa := clone(a)
	bb := clone(b)
	sort.Strings(aa)
	sort.Strings(bb)
	for i := range aa {
		if aa[i] != bb[i] {
			return false
		}
	}
	return true
}

// clone returns a deep copy of a slice of strings.
func clone(a []string) []string {
	b := make([]string, len(a))
	for i := range a {
		b[i] = a[i]
	}
	return b
}

func start(t *testing.T, params []string, overlap time.Duration) *test {
	test := &test{
		t,
		newLineWriter(false),
		newLineWriter(false),
		params,
		0,
	}
	c := testbin.Build() + " " + placeholder
	go alternate(c, placeholder, params, overlap, newNilWriter(), test.cmdStdout, test.cmdStderr)
	return test
}

func sendUsr1() {
	process().Signal(syscall.SIGUSR1)
}

func stop() {
	testExit <- struct{}{}
	time.Sleep(one)
}

func process() *os.Process {
	p, err := os.FindProcess(os.Getpid())
	if err != nil {
		panic(err)
	}
	return p
}

func TestSameStrings(t *testing.T) {
	tests := []struct {
		a    []string
		b    []string
		same bool
	}{
		{[]string{"a", "b"}, []string{"a", "b"}, true},
		{[]string{"b", "a"}, []string{"a", "b"}, true},
		{[]string{"a", "b", "c"}, []string{"a", "b"}, false},
	}

	for _, test := range tests {
		same := sameStrings(test.a, test.b)
		if test.same != same {
			t.Errorf("For values %q and %q, expected same to be %t, was %t",
				test.a, test.b, test.same, same)
		}
	}
}

func TestLineWriter(t *testing.T) {
	type action struct {
		reset bool
		write string
	}
	tests := []struct {
		actions []action
		lines   []string
{'fraction_non_alphanumeric': 0.15866156315594518, 'fraction_numerical': 0.0024694406716878627, 'mean_word_length': 3.3571812802582035, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5240397', 'n_tokens_mistral': 3192, 'n_tokens_neox': 3049, 'n_words': 861}